SwiftUI采用MVVM架构时分类视图点击后不更新问题排查
SwiftUI分类菜单选中状态不更新问题解决
问题描述
我有两个SwiftUI视图CategoriesListView(导航菜单)和CategoryView(可点击项),想要实现:
- 初始仅第一个分类高亮
- 点击某个分类时,该分类显示高亮图标
- 之前选中的分类取消高亮
但点击后视图毫无变化,刚接触SwiftUI,想知道是不是需要在ViewModel里调整?
原视图代码
struct CategoriesListView: View { @Environment(CategoriesListViewModel.self) var categoriesListViewModel var selectedIndex: Int { categoriesListViewModel.categories.firstIndex(where: { $0.isSelected == true }) ?? 0 } var body: some View { @Bindable var categoriesListViewModel = categoriesListViewModel ScrollView(.horizontal) { HStack(alignment: .top, spacing: 12) { Spacer() ForEach(Array(categoriesListViewModel.categories.enumerated()), id: \.offset) { index, element in CategoryView(categoryViewModel: $categoriesListViewModel.categories[index]).onTapGesture { categoriesListViewModel.categories[selectedIndex].isSelected = false categoriesListViewModel.categories[index].isSelected = true CategoryView(categoryViewModel: $categoriesListViewModel.categories[index]) } } } } } } struct CategoryView: View { @Binding var categoryViewModel: CategoryViewModel var body: some View { if categoryViewModel.isSelected { categoryViewModel.category.highlightedIconImage } else { categoryViewModel.category.iconImage } } }
原ViewModel代码
@Observable class CategoriesListViewModel { var categories: [CategoryViewModel] = [] var currentSelection: Int = 0 var previousSelection: Int? = nil init(categories: [CategoryViewModel], currentSelection: Int, previousSelection: Int? = nil) { self.categories = setCategoriesToShow() self.currentSelection = currentSelection self.previousSelection = previousSelection } func setCategoriesToShow() -> [CategoryViewModel] { var categoriesToShow = [CategoryViewModel]() let resortCategory = Category( identifier: "a", title: "Resort", outfits: [], iconImage: Image("ResortUnselected"), highlightedIconImage: Image("ResortSelected") ) var europeCategory = Category( identifier: "b", title: "Europe", outfits: [], iconImage: Image("EuropeUnselected"), highlightedIconImage: Image("EuropeSelected") ) var brunchCategory = Category( identifier: "c", title: "Brunch", outfits: [], iconImage: Image("BrunchUnselected"), highlightedIconImage: Image("BrunchSelected") ) var athleisureCategory = Category( identifier: "d", title: "Athleisure", outfits: [], iconImage: Image("AthleisureUnselected"), highlightedIconImage: Image("AthleisureSelected") ) var workCategory = Category( identifier: "e", title: "Work", outfits: [], iconImage: Image("WorkUnselected"), highlightedIconImage: Image("WorkSelected") ) categoriesToShow.append(CategoryViewModel(category: resortCategory, isSelected: true)) categoriesToShow.append(CategoryViewModel(category: europeCategory, isSelected: false)) categoriesToShow.append(CategoryViewModel(category: brunchCategory, isSelected: false)) categoriesToShow.append(CategoryViewModel(category: athleisureCategory, isSelected: false)) categoriesToShow.append(CategoryViewModel(category: workCategory, isSelected: false)) return categoriesToShow } } class CategoryViewModel: ObservableObject, Identifiable { var category: Category var isSelected: Bool init(category: Category, isSelected: Bool) { self.category = category self.isSelected = isSelected } }
问题原因
- 状态变更无法触发刷新:
CategoryViewModel的isSelected未标记为可观察属性,且CategoriesListViewModel的@Observable仅监听数组本身的增删改,不会响应数组元素内部的属性变化。 - 无效视图操作:
onTapGesture里创建CategoryView的代码完全无用,只是生成了一个视图实例,并未替换界面上的现有视图。 - 状态管理分散:在视图中直接操作数组元素的选中状态,逻辑分散且易出错。
修复方案
1. 调整ViewModel结构
将CategoryViewModel改为@Observable修饰,集中管理选中切换逻辑:
import SwiftUI // 补充Category结构体的完整定义 struct Category: Identifiable { let id = UUID() var identifier: String var title: String var outfits: [Any] var iconImage: Image var highlightedIconImage: Image } @Observable class CategoriesListViewModel { var categories: [CategoryViewModel] = [] init() { self.categories = setCategoriesToShow() } func setCategoriesToShow() -> [CategoryViewModel] { let resortCategory = Category( identifier: "a", title: "Resort", outfits: [], iconImage: Image("ResortUnselected"), highlightedIconImage: Image("ResortSelected") ) let europeCategory = Category( identifier: "b", title: "Europe", outfits: [], iconImage: Image("EuropeUnselected"), highlightedIconImage: Image("EuropeSelected") ) let brunchCategory = Category( identifier: "c", title: "Brunch", outfits: [], iconImage: Image("BrunchUnselected"), highlightedIconImage: Image("BrunchSelected") ) let athleisureCategory = Category( identifier: "d", title: "Athleisure", outfits: [], iconImage: Image("AthleisureUnselected"), highlightedIconImage: Image("AthleisureSelected") ) let workCategory = Category( identifier: "e", title: "Work", outfits: [], iconImage: Image("WorkUnselected"), highlightedIconImage: Image("WorkSelected") ) return [ CategoryViewModel(category: resortCategory, isSelected: true), CategoryViewModel(category: europeCategory, isSelected: false), CategoryViewModel(category: brunchCategory, isSelected: false), CategoryViewModel(category: athleisureCategory, isSelected: false), CategoryViewModel(category: workCategory, isSelected: false) ] } // 集中处理选中切换逻辑 func selectCategory(at index: Int) { categories.indices.forEach { categories[$0].isSelected = false } categories[index].isSelected = true } } @Observable class CategoryViewModel: Identifiable { let id = UUID() var category: Category var isSelected: Bool init(category: Category, isSelected: Bool) { self.category = category self.isSelected = isSelected } }
2. 优化视图代码
移除无效代码,调用ViewModel的统一方法处理选中:
struct CategoriesListView: View { @Environment(CategoriesListViewModel.self) var categoriesListViewModel var body: some View { ScrollView(.horizontal) { HStack(alignment: .top, spacing: 12) { Spacer() ForEach($categoriesListViewModel.categories) { $categoryVM in CategoryView(categoryViewModel: $categoryVM) .onTapGesture { if let index = categoriesListViewModel.categories.firstIndex(where: { $0.id == categoryVM.id }) { categoriesListViewModel.selectCategory(at: index) } } } } } } } struct CategoryView: View { @Binding var categoryViewModel: CategoryViewModel var body: some View { categoryViewModel.isSelected ? categoryViewModel.category.highlightedIconImage : categoryViewModel.category.iconImage } }
关键修改说明
@Observable修饰CategoryViewModel:让isSelected的变化能被SwiftUI监听,触发视图自动刷新。- 集中状态管理:把选中切换逻辑封装到
selectCategory方法中,避免视图直接操作数组,逻辑更清晰易维护。 - 移除无效代码:删掉
onTapGesture里创建CategoryView的无用代码,视图更新完全由状态驱动。
内容的提问来源于stack exchange,提问作者jane-bun
相关产品推荐
相关产品推荐

