You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

迁移至@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 00:25:07