迁移至@Observable宏后UI无法正确反映Reaction状态的问题
问题:迁移至@Observable宏后UI状态异常
将ViewModel从ObservableObject迁移至@Observable宏后,出现UI无法正确反映选中状态的问题——点击Reaction选项时,会出现多个选项同时显示为选中的异常情况,此前使用ObservableObject时功能正常。
相关代码如下:
Reaction枚举
enum Reaction: CaseIterable, Identifiable { case like, dislike, love var id: Self { self } var systemImageName: String { switch self { case .like: "hand.thumbsup" case .dislike: "hand.thumbsdown" case .love: "heart" } } var selectedSystemImageName: String { systemImageName + ".fill" } }
ViewModel代码
@Observable class ViewModel { var reaction: Reaction? func set(_ newReaction: Reaction) async { // 此处为数据库更新逻辑 reaction = newReaction == reaction ? nil : newReaction } }
视图代码
struct ContentView: View { @State private var viewModel = ViewModel() var body: some View { HStack(spacing: 40) { ForEach(Reaction.allCases) { reaction in Image(systemName: viewModel.reaction == reaction ? reaction.selectedSystemImageName : reaction.systemImageName) .font(.largeTitle) .onTapGesture { Task { await viewModel.set(reaction) } } } } } }
原因分析
@Observable宏和ObservableObject的状态更新线程机制存在差异:
- 使用
ObservableObject时,@Published属性的更新会自动切换到主线程触发UI刷新; - 而
@Observable的属性更新不会主动处理线程切换,如果在异步方法(如async标记的set方法)中更新属性,代码可能在后台线程执行,导致UI无法及时、正确地同步状态,最终出现多个选项同时“选中”的视觉错误。
解决方案
确保状态更新操作在主线程执行,有两种常用方式:
方式一:用MainActor.run包裹状态更新
修改ViewModel的set方法,将reaction的赋值逻辑放在MainActor.run中:
@Observable class ViewModel { var reaction: Reaction? func set(_ newReaction: Reaction) async { // 数据库更新逻辑(若为异步操作,可保留在后台执行) await MainActor.run { reaction = newReaction == reaction ? nil : newReaction } } }
方式二:给ViewModel标注@MainActor
直接将ViewModel标记为@MainActor,确保所有属性访问和方法执行都在主线程:
@MainActor @Observable class ViewModel { var reaction: Reaction? func set(_ newReaction: Reaction) async { // 数据库更新逻辑 reaction = newReaction == reaction ? nil : newReaction } }
两种方式都能保证reaction的状态更新会立即触发UI的正确刷新,解决多选项同时选中的异常问题。
内容的提问来源于stack exchange,提问作者Guillaume Ramey
相关产品推荐
相关产品推荐

