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
相关产品推荐
相关产品推荐

