[SwiftUI]自定义滚动菜单栏进行PageView页面切换

如图,自定义一个菜单栏,要求点击菜单按钮和滚动翻页步调统一。

首先有个分类模型

import Foundation

struct CategoryModel: Hashable {
    
    var categoryID: Int = 0
    var categoryName: String = ""
    
}

基础实现代码如下,点击菜单和滚动页面进行翻页都能正常工作,见示意图。

import SwiftUI

struct FlashcardView: View {
    @Environment(.presentationMode) var presentationMode

    @State private var listArr: [CategoryExModel] = []
    @State private var selectedTab: Int = 0

    var body: some View {
        VStack {
            // 自定义的水平滚动菜单
            ScrollView(.horizontal, showsIndicators: false) {
                LazyHStack(spacing: 16) {
                    ForEach(0..<listArr.count, id: .self) { index in
                        Button(action: {
                            withAnimation {
                                selectedTab = index
                            }
                        }) {
                            ZStack {
                                if selectedTab == index {
                                    Image("icon-prectice-menu-bg")
                                        .frame(width: 16, height: 16)
                                        .offset(x: 10, y: 5) // 向下偏移5像素
                                }
                                Text(listArr[index].categoryName)
                                    .frame(minWidth: 40, minHeight: 44)
                                    .font(selectedTab == index ? Font.medium(17) : Font.regular(17))
                                    .foregroundColor(selectedTab == index ? Color.textAux33 : Color.textAux66)
                                    .padding(.horizontal, 8)
                            }
                        }
                        .contentShape(Rectangle())
                        .cornerRadius(10)
                    }
                }
                .frame(height: 44)
                .padding(.horizontal)
            }
            
            // 页面内容
            TabView(selection: $selectedTab) {
                ForEach(0..<listArr.count, id: .self) { index in
                    FlashcardContentView(categoryItem: listArr[index])
                        .tag(index)
                }
            }
            .tabViewStyle(.page(indexDisplayMode: .never)) // 隐藏系统的页码指示器
        }
        .themeBackground()
        .navigationBarHidden(true) // 隐藏顶部的导航栏
        .navigationBarBackButtonHidden(true) // 隐藏返回按钮
        .edgesIgnoringSafeArea(.all)
        .onAppear {
            requestCategoryList()
        }
    }
    
    private func goBack() {
         presentationMode.wrappedValue.dismiss()
    }
    
}

extension FlashcardView {
    
    /// 获取二级分类
    private func requestCategoryList() {
        DispatchQueue.global().async {
            QuestionDatabase.getDatabase().executeInDatabase { database in
                let categoryArr = database.loadSecondCategories()
                DispatchQueue.main.async {
                    listArr = categoryArr
                }
            }
        }
    }
    
}

示意图:

先从左到右点击菜单栏中的按钮,然后在从右到左滑动内容进行翻页。

上面的效果比较差,下面进一步优化,为菜单栏选中按钮添加跟随效果。

只需使用ScrollViewReader包裹ScrollView,然后在ScrollView添加onChange中指定元素居中。

// 使用 ScrollViewReader 包裹 ScrollView
ScrollViewReader { proxy in
    ScrollView(.horizontal, showsIndicators: false) {
        代码不变
    }
    .onChange(of: selectedTab) { num in
        // 当 selectedTab 改变时,滚动到指定的元素让它居中
        withAnimation {
            proxy.scrollTo(selectedTab, anchor: .center)
        }
    }
}

示意图:

先从左到右点击菜单栏中的按钮,然后在从右到左滑动内容进行翻页。