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

基于@Observable ViewModel初始化@State属性selectedOption为何失败?

问题原因分析

你遇到的问题核心在于**@State属性的初始化时机限制**:

  • @State是SwiftUI负责管理的属性包装器,视图的init方法执行时,@State的底层存储还未被SwiftUI完全初始化。此时直接给selectedOption赋值,这个值不会被SwiftUI的状态系统保留,后续视图完成初始化后,@State会被重置为声明时的默认值(也就是nil)。
  • 控制台能打印出匹配的Option实例,只是因为你直接访问了viewModel的计算结果,但这个实例并没有被@State的存储正确持有。
解决方案

方案1:通过onAppear延迟赋值

在视图完成初始化后(onAppear阶段),@State的存储已经就绪,此时赋值会被正确保留:

@State var selectedOption: Option?

init() {
    // 保留打印调试代码,移除赋值逻辑
    print(viewModel.options.count)
    print(viewModel.options.first { $0.identifier == Settings.currentLanguage })
    print(viewModel.options.map { $0.identifier })
    print(Settings.currentLanguage)
}

var body: some View {
    ZStack {
        ScrollView {
            // 原有视图内容...
        }
        .padding(20)
        .onAppear {
            // 在视图出现时完成赋值
            selectedOption = viewModel.options.first { $0.identifier == Settings.currentLanguage }
        }
        BottomSheetView(isShowing: $isSelected) {
            PickerSelect(selectedOption: $selectedOption, options: viewModel.options)
        }
    }
}

方案2:将状态移至ViewModel管理(更符合MVVM)

把selectedOption的状态放到ViewModel中,利用@Observable的特性管理,避免@State的初始化限制:

修改ViewModel:

@Observable
class SettingsViewModel {
    let settings = Settings.shared
    var selectedOption: Option?
    
    var options: [Option] {
        Settings.languages.map { language in
            Option(title: settings.languageTitle(for: language), value: settings.keywordsTitle(for: language), identifier: language)
        }
    }
    
    init() {
        // 在ViewModel初始化时直接完成匹配赋值
        selectedOption = options.first { $0.identifier == Settings.currentLanguage }
    }
}

修改视图:

@StateObject var viewModel = SettingsViewModel()

var body: some View {
    ZStack {
        ScrollView {
            VStack(spacing: 40) {
                InAppPurchaseView()
                HStack {
                    TextLabel("settings.language".localized)
                    Spacer()
                    VStack(alignment: .leading) {
                        // 直接使用ViewModel中的selectedOption
                        TextLabel(viewModel.selectedOption?.title, size: 24, weight: .bold)
                        TextLabel(viewModel.selectedOption?.value)
                    }
                }
                .onTapGesture {
                    isSelected = true
                }
            }
        }
        .padding(20)
        BottomSheetView(isShowing: $isSelected) {
            // 绑定ViewModel的selectedOption
            PickerSelect(selectedOption: $viewModel.selectedOption, options: viewModel.options)
        }
    }
}

方案3:直接在@State声明时初始化

如果viewModel在视图声明@State前已经初始化完成,可以直接在@State的声明中赋值:

// 假设viewModel是@StateObject或已提前初始化
@StateObject var viewModel = SettingsViewModel()
@State var selectedOption: Option? = SettingsViewModel().options.first { $0.identifier == Settings.currentLanguage }

// 移除init中的赋值逻辑

注意:这种方式需要确保SettingsViewModel()的初始化不会产生额外副作用,且options的计算结果是稳定的。

内容的提问来源于stack exchange,提问作者kunass2

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 14:38:30