@Observable类成员无法触发UI更新问题排查与修复咨询
问题描述
使用@Observable宏实现状态管理,尝试将其作为环境对象及视图内Bindable变量使用:
- 初始渲染正常,点击矩形时日志能打印状态变化(如下),但视图颜色未更新
- 添加
let _ = Self._printChanges()后,仅在视图出现时输出一次ContentView: @self, @identity, _fooWrapper changed.,点击无后续更新
日志示例:
from environment zoo false from environment zoo true from bindable false from environment false from bindable true from environment true from environment false from environment true from environment false from bindable false from bindable true from bindable false ...
原代码
FooApp.swift
import SwiftUI class Bar { var selected: Bool init() { self.selected = false } } protocol FooProtocol { var bar: Bar { get set } } @Observable class FooWrapper { var foo: any FooProtocol init(foo: some FooProtocol) { self.foo = foo } } class Foo { var bar: Bar init() { self.bar = .init() } } extension Foo: FooProtocol {} // Fully concrete, no existentials. @Observable class ZooWrapper { var foo: Foo init() { self.foo = Foo() } } @main struct FooApp: App { @State private var foo = FooWrapper(foo: Foo()) @State private var zoo = ZooWrapper() var body: some Scene { WindowGroup { ContentView().environment(foo) } } }
ContentView.swift
import SwiftUI struct ContentView: View { @Environment(FooWrapper.self) private var fooWrapper var body: some View { @Bindable var fooWrapper1 = FooWrapper(foo: Foo()) HStack { Rectangle() .fill(zooWrapper.foo.bar.selected ? Color.green : .blue) .frame(width: 50, height: 50) .onTapGesture { var bar = zooWrapper.foo.bar print("from environment zoo \(bar.selected)") bar.selected.toggle() } Rectangle() .fill(fooWrapper.foo.bar.selected ? Color.purple : .yellow) .frame(width: 50, height: 50) .onTapGesture { var bar = fooWrapper.foo.bar print("from environment \(bar.selected)") bar.selected.toggle() } Rectangle() .fill(fooWrapper1.foo.bar.selected ? Color.mint : .red) .frame(width: 50, height: 50) .onTapGesture { var bar = fooWrapper1.foo.bar print("from bindable \(bar.selected)") bar.selected.toggle() } } } }
问题原因
Bar类未标记@Observable:SwiftUI仅能感知被@Observable标记的类的属性变化,Bar作为存储核心状态的类未加该宏,导致状态修改无法触发视图刷新。- 视图内
@Bindable变量生命周期错误:body内定义的fooWrapper1会在每次视图刷新时重新创建,修改的是临时实例的状态,无法持久化也无法触发视图更新。 ZooWrapper未传递到视图:原ContentView中直接使用zooWrapper但未定义该变量,属于代码逻辑漏洞。- 冗余的状态拷贝:
onTapGesture中拷贝Bar实例的引用(var bar = ...)属于冗余写法,虽不影响状态修改,但增加代码复杂度。
修复方案
1. 为Bar添加@Observable宏
让SwiftUI能追踪selected属性的变化:
@Observable class Bar { var selected: Bool init() { self.selected = false } }
2. 修正ContentView的变量生命周期
将fooWrapper1移到视图属性中,避免每次刷新重建:
struct ContentView: View { @Environment(FooWrapper.self) private var fooWrapper @Environment(ZooWrapper.self) private var zooWrapper // 注入ZooWrapper环境对象 @State private var fooWrapper1 = FooWrapper(foo: Foo()) // 移到属性层 var body: some View { @Bindable var bindableFooWrapper1 = fooWrapper1 // 用@Bindable包装State变量 HStack { // ... 其他视图代码 } } }
3. 传递ZooWrapper到视图
在FooApp中添加环境注入:
@main struct FooApp: App { @State private var foo = FooWrapper(foo: Foo()) @State private var zoo = ZooWrapper() var body: some Scene { WindowGroup { ContentView() .environment(foo) .environment(zoo) // 注入ZooWrapper } } }
4. 简化状态修改逻辑
直接修改原对象属性,去掉冗余的引用拷贝:
// 第一个矩形的点击事件 .onTapGesture { zooWrapper.foo.bar.selected.toggle() print("from environment zoo \(zooWrapper.foo.bar.selected)") } // 第二个矩形的点击事件 .onTapGesture { fooWrapper.foo.bar.selected.toggle() print("from environment \(fooWrapper.foo.bar.selected)") } // 第三个矩形的点击事件 .onTapGesture { bindableFooWrapper1.foo.bar.selected.toggle() print("from bindable \(bindableFooWrapper1.foo.bar.selected)") }
内容的提问来源于stack exchange,提问作者g i i k
相关产品推荐
相关产品推荐

