You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中Foreach选中按钮背景色及默认选中首个项问题

解决SwiftUI横向子分类按钮默认选中首个项的问题

当前代码无法默认选中首个子分类的核心原因:subcategory视图的onAppear会在视图首次渲染时触发,但此时ViewModel的subcategories数组还未完成异步API加载,无法获取首个子分类;后续数据加载完成后,onAppear不会再次执行,导致选中状态未更新。

以下是两种可行的解决方案:

方案一:在View中监听子分类数据变化

通过onReceive监听vm.subcategories的发布变化,当数组加载完成且选中状态为空时,自动设置选中首个子分类:

修改SubCatProductView的subcategory扩展部分:

private extension SubCatProductView {
    var subcategory: some View {
        ScrollView(.horizontal) {
            HStack {
                ForEach(vm.subcategories, id: \.id) { subcategory in
                    Button {
                        print("selected SubCategory",subcategory.name)
                        selectedSubCat = subcategory.name
                        vm.getProducts(subcat_id: subcategory.id)
                    } label: {
                        SubCatView(subcategory: subcategory)
                    }
                    .background(selectedSubCat == subcategory.name ? Color.yellow : Color.clear)
                }
            }
        }
        // 替换原onAppear,监听subcategories数组的变化
        .onReceive(vm.$subcategories) { subcats in
            if let first = subcats.first, selectedSubCat.isEmpty {
                selectedSubCat = first.name
            }
        }
    }
}

方案二:将选中状态移至ViewModel(更符合MVVM架构)

把选中状态的管理放到ViewModel中,在子分类数据加载完成后直接设置选中项,View仅负责响应状态变化:

1. 修改ViewModel

final class SubCategoryViewModel: ObservableObject {
    @Published var subcategories : [SubData] = []
    @Published var products : [ProductData] = []
    // 用子分类ID作为选中标识(比名称更可靠,避免名称重复)
    @Published var selectedSubCatId: String = ""
    
    func subcatRequest() {
        let url = "https://auroraa.site/suresh-packers/admin/api/get-sub-category-api.php"
        
        AF.request(url, method: .post,
                   parameters: ["type": "get_sub_category","category": "1"],
                   encoding: URLEncoding.default,
                   headers: nil,
                   interceptor: nil).response { responce in
            
            switch responce.result {
            case .success(let data):
                do {
                    let jsonData = try JSONDecoder().decode(SubCategory.self, from: data!)
                    print("Category Names : ",jsonData)
                    self.subcategories = jsonData.data
                    // 加载完成后直接设置首个子分类为选中状态
                    if let firstSubcat = self.subcategories.first {
                        self.selectedSubCatId = firstSubcat.id
                        self.getProducts(subcat_id: firstSubcat.id)
                    }
                } catch {
                    print(error.localizedDescription)
                }
            case .failure(let error):
                print(error.localizedDescription)
            }
        }
    }
    
    // getProducts方法保持不变
    func getProducts(subcat_id : String) {
        // ... 原有代码
    }
}

2. 修改View

移除View中的selectedSubCat状态变量,改用ViewModel的selectedSubCatId:

struct SubCatProductView: View {
    @StateObject private var vm = SubCategoryViewModel()
    @State private var hasError = false
    
    var body: some View {
        VStack(alignment: .leading) {
            subcategory
            product
        }
        .task {
            vm.subcatRequest()
       }
    }
}

private extension SubCatProductView {
    var subcategory: some View {
        ScrollView(.horizontal) {
            HStack {
                ForEach(vm.subcategories, id: \.id) { subcategory in
                    Button {
                        print("selected SubCategory",subcategory.name)
                        vm.selectedSubCatId = subcategory.id
                        vm.getProducts(subcat_id: subcategory.id)
                    } label: {
                        SubCatView(subcategory: subcategory)
                    }
                    .background(vm.selectedSubCatId == subcategory.id ? Color.yellow : Color.clear)
                }
            }
        }
    }
    
    // product视图保持不变
    var product: some View {
        List {
            ForEach(vm.products, id: \.id) { product in
                ProductView(product: product)
            }
        }
    }
}

说明:方案二更推荐,状态管理集中在ViewModel中,避免View与ViewModel的状态不一致问题,同时用ID作为选中标识比名称更稳定。

内容的提问来源于stack exchange,提问作者SasidharanIOS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 02:37:34