SwiftUI中更新@Observable ViewModel属性为何触发所有关联视图重绘?
问题原因分析
你遇到的问题本质是父视图TestAlarms通过@Bindable观察整个AlarmLevelSetupViewModel对象,导致ViewModel的任意属性变更都会触发父视图body重计算,进而重新初始化两个AlarmLevelRowView实例。尽管SwiftUI自带视图diffing优化,但如果子视图未做依赖隔离,就会出现无关视图也重绘的情况。
解决方案
推荐两种实用的解决思路:
1. 让子视图遵循Equatable并使用EquatableView包裹
通过让AlarmLevelRowView实现Equatable协议,明确视图参数的相等判定规则,再用EquatableView包裹子视图,SwiftUI会自动跳过参数未变化的视图重绘。
步骤1:修改AlarmLevelRowView实现Equatable
struct AlarmLevelRowView: View, Equatable { static func == (lhs: Self, rhs: Self) -> Bool { lhs.title == rhs.title && lhs.color == rhs.color && lhs.radiationType == rhs.radiationType && lhs.values == rhs.values && lhs.selectedValue.wrappedValue == rhs.selectedValue.wrappedValue } let title: String let color: Color let radiationType: RadiationType // 假设这是你的自定义枚举类型 let values: [Decimal] @Binding var selectedValue: Decimal var body: some View { // 原视图渲染逻辑 } }
步骤2:在父视图中用EquatableView包裹子视图
struct TestAlarms: View { @Bindable var viewModel: AlarmLevelSetupViewModel let values: [Decimal] = [0, 1, 2, 3, 4, 5] var body: some View { EquatableView(content: AlarmLevelRowView( title: "level 2", color: .accentRed, radiationType: .radiationLevel(.countRate), values: values, selectedValue: $viewModel.displayedValueLevel2 )) EquatableView(content: AlarmLevelRowView( title: "level 1", color: .accentYellow, radiationType: .radiationLevel(.countRate), values: values, selectedValue: $viewModel.displayedValueLevel1 )) } }
当displayedValueLevel1变更时,第一个AlarmLevelRowView的所有参数均未变化,EquatableView会直接跳过它的重绘流程。
2. 拆分ViewModel为独立的可观察对象
将两个属性拆分到独立的@Observable类中,让父视图分别持有两个@Bindable对象,从根源上隔离属性的观察范围,单个属性的变更只会触发对应子视图重绘。
步骤1:定义单个等级的ViewModel
@Observable final class SingleAlarmLevelViewModel { var displayedValue: Decimal init(initialValue: Decimal) { self.displayedValue = initialValue } }
步骤2:修改父视图使用拆分后的ViewModel
struct TestAlarms: View { @Bindable var level1VM: SingleAlarmLevelViewModel @Bindable var level2VM: SingleAlarmLevelViewModel let values: [Decimal] = [0, 1, 2, 3, 4, 5] init() { self.level1VM = SingleAlarmLevelViewModel(initialValue: 1) self.level2VM = SingleAlarmLevelViewModel(initialValue: 2) } var body: some View { AlarmLevelRowView( title: "level 2", color: .accentRed, radiationType: .radiationLevel(.countRate), values: values, selectedValue: $level2VM.displayedValue ) AlarmLevelRowView( title: "level 1", color: .accentYellow, radiationType: .radiationLevel(.countRate), values: values, selectedValue: $level1VM.displayedValue ) } }
内容的提问来源于stack exchange,提问作者Matvei Kozin
相关产品推荐
相关产品推荐

