SwiftUI List视图无法根据状态变量动态更新显示/隐藏元素求助
问题:SwiftUI列表从配置页返回后未根据ObservableObject属性更新渲染
我在Watch App的SwiftUI中实现了一个包含NavigationLink的列表,每个列表项通过isVisible布尔值控制是否显示。另有一个配置页,通过Toggle组件设置各元素的显示/隐藏状态。
问题是:从配置页返回主视图后,主视图并未根据更新后的isVisible值重新渲染,if (hv.isVisible)的判断没有重新执行。我已确认布尔值已被修改,重启应用后列表能正确显示对应元素。
可复现代码
import SwiftUI enum ValueType: String { case type1 = "T1" case type2 = "T2" case type3 = "T3" } class DummyValueObject : Identifiable, ObservableObject { let type: ValueType let symbolName: String @Published var isVisible: Bool = true { didSet { print("ValueObject type=\(type.rawValue), isVisible = \(isVisible)") } } init(type: ValueType, symbolName: String) { self.type = type self.symbolName = symbolName } } class DummyViewModel : ObservableObject { static let instance = DummyViewModel() @Published var values: [DummyValueObject] = [ DummyValueObject(type: .type1, symbolName: "heart.fill"), DummyValueObject(type: .type2, symbolName: "wind"), DummyValueObject(type: .type3, symbolName: "scalemass.fill") ] } struct DummySettingCellView: View { @ObservedObject var value: DummyValueObject var body: some View { HStack { Image(systemName: value.symbolName) Text(value.type.rawValue) Toggle("", isOn: $value.isVisible) } } } struct DummySettingsView: View { @ObservedObject var model = DummyViewModel.instance var body: some View { VStack { List(model.values) { v in DummySettingCellView(value: v) } } } } struct DummyView: View { @ObservedObject var model = DummyViewModel.instance var body: some View { VStack { NavigationLink(destination: DummySettingsView()) { Image(systemName: "wrench") } Spacer() List(model.values, id:\.type.rawValue) { v in if (v.isVisible) { HStack { Image(systemName: v.symbolName) Text(v.type.rawValue) } } } } } }
原因分析
问题出在视图的更新触发逻辑上:
- 主视图的
List依赖DummyViewModel的@Published var values数组作为数据源,只有当数组本身发生变化(比如添加、删除元素)时,才会触发ViewModel的更新通知,进而刷新主视图。 - 而每个
DummyValueObject的isVisible是独立的@Published属性,修改它只会触发自身的ObservableObject更新,但主视图并没有监听单个DummyValueObject的变化,因此主视图不会重新执行if (v.isVisible)的判断。
解决方案
方案1:在列表中监听单个元素的变化(推荐,iOS13+)
在主视图的List闭包中,将元素声明为@ObservedObject,让视图监听每个DummyValueObject的属性变化:
// 修改DummyView中的List部分 List(model.values, id:\.type.rawValue) { @ObservedObject v in if v.isVisible { HStack { Image(systemName: v.symbolName) Text(v.type.rawValue) } } }
如果你的项目基于iOS17+/watchOS10+,可以使用更轻量的@Bindable替代@ObservedObject:
List(model.values, id:\.type.rawValue) { @Bindable v in if v.isVisible { HStack { Image(systemName: v.symbolName) Text(v.type.rawValue) } } }
方案2:让ViewModel监听元素属性变化
在DummyViewModel中订阅每个元素的isVisible变化,一旦变化就手动发送ViewModel的更新通知:
import Combine // 需要导入Combine框架 class DummyViewModel : ObservableObject { static let instance = DummyViewModel() @Published var values: [DummyValueObject] = [ DummyValueObject(type: .type1, symbolName: "heart.fill"), DummyValueObject(type: .type2, symbolName: "wind"), DummyValueObject(type: .type3, symbolName: "scalemass.fill") ] private var cancellables = Set<AnyCancellable>() init() { // 订阅每个元素的isVisible变化 for value in values { value.$isVisible .sink { [weak self] _ in self?.objectWillChange.send() // 手动触发ViewModel更新 } .store(in: &cancellables) } } }
这种方式不需要修改主视图的代码,ViewModel会统一管理所有元素的变化通知。
方案3:将元素改为值类型(Struct)
如果业务允许,把DummyValueObject从class改为struct,因为值类型的属性修改会触发数组的变化,进而触发ViewModel的@Published更新:
struct DummyValueObject : Identifiable { let id = UUID() // 需要添加Identifiable的id属性 let type: ValueType let symbolName: String var isVisible: Bool = true { didSet { print("ValueObject type=\(type.rawValue), isVisible = \(isVisible)") } } init(type: ValueType, symbolName: String) { self.type = type self.symbolName = symbolName } }
同时需要修改配置页的Toggle绑定,因为struct是值类型,需要通过ViewModel的数组索引来修改:
// 修改DummySettingCellView struct DummySettingCellView: View { let index: Int @ObservedObject var model: DummyViewModel var body: some View { let value = model.values[index] HStack { Image(systemName: value.symbolName) Text(value.type.rawValue) Toggle("", isOn: $model.values[index].isVisible) } } } // 修改DummySettingsView中的List List(model.values.indices, id: \.self) { index in DummySettingCellView(index: index, model: model) }
这种方式适合不需要将元素作为独立ObservableObject传递的场景。
内容的提问来源于stack exchange,提问作者imodude
相关产品推荐
相关产品推荐

