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

SwiftUI本地变量初始化异常?MenuBar Extra数据同步问题求解

问题:MenuBar Extra中编辑视图的原始值初始化滞后问题

我正在开发一个与当前应用关联的MenuBar Extra,通过获取当前应用的bundle id来获取或创建SampleData数据,并尝试在视图间传递数据进行编辑。我希望在编辑视图中保留原始值的副本用于对比,因此创建了一个本地@State变量original,并在.onAppear中赋值。但问题是,编辑视图能正确显示当前bundle id对应的数据,而original却显示上一次的旧值。

具体表现:

  • 当聚焦Xcode打开应用时,显示正常;
  • 切换到Firefox打开应用时,“Original”显示的是Xcode对应的数据;
  • 再次打开Firefox时,“Original”才更新为正确值。

附相关代码:

import SwiftUI
import SwiftData

@main
struct XBApp: App {
    @State var bundleID: String = ""

    var body: some Scene {
        MenuBarExtra("Something", systemImage: "questionmark.bubble") {
            VStack {
                SampleContent(bundleID: $bundleID)
                    .modelContainer(for: SampleData.self)
                    .padding(0)
            }
            .onAppear {
                bundleID = NSWorkspace.shared.frontmostApplication!.bundleIdentifier!
                dbug(bundleID)
            }
        }
        .menuBarExtraStyle(.window)
    }
}

@Model
class SampleData {
    @Attribute(.unique) var bundle: String
    var data: String
    init(bundle: String, data: String) {
        self.bundle = bundle
        self.data = data
    }
}

struct EditSampleData: View {
    @Environment(\.modelContext) private var modelContext
    @Bindable var sampleData: SampleData
    @State var original: String = ""
    var body: some View {
        Form {
            Text(sampleData.bundle)
            Text(original)          //  out of step - shows previous version
            TextField("Data", text: $sampleData.data)
        }
        .onAppear {
            original = sampleData.data
        }
    }
}

struct SampleContent: View {
    @Binding var bundleID: String
    @Environment(\.modelContext) private var modelContext
    @State var sampleData: SampleData?

    func loadData(bundleID: String) -> SampleData? {
        let filter = FetchDescriptor<SampleData>(predicate: #Predicate { $0.bundle == bundleID })
        do {
            if let sd = try modelContext.fetch(filter).first {
                return sd
            } else {
                let sd = SampleData(bundle: bundleID, data: "New Data: \(bundleID)")
                modelContext.insert(sd)
                return sd
            }
        } catch {
            return nil
        }
    }

    var body: some View {
        VStack {
            if let sd = sampleData {
                Text(sd.bundle)
                EditSampleData(sampleData: sd)
            }
        }
        .onAppear {
            sampleData = loadData(bundleID: bundleID)!
        }
    }
}

问题分析

.onAppear 并非每次视图数据更新都会触发:当切换不同bundle对应的SampleData实例时,EditSampleData视图可能被复用,导致.onAppear没有重新执行,original变量仍保留旧值。同时@State变量的生命周期与视图绑定,状态更新不及时会加剧这个问题。

修复方案

1. 监听SampleData实例变化更新原始值

在EditSampleData中新增.onChange监听sampleData实例的变化,确保每次切换bundle时,original都能同步最新的初始值:

struct EditSampleData: View {
    @Environment(\.modelContext) private var modelContext
    @Bindable var sampleData: SampleData
    @State var original: String = ""
    
    var body: some View {
        Form {
            Text(sampleData.bundle)
            Text(original)
            TextField("Data", text: $sampleData.data)
        }
        .onAppear {
            original = sampleData.data
        }
        // 监听sampleData实例变化,同步原始值
        .onChange(of: sampleData) { newSample in
            original = newSample.data
        }
    }
}

2. 优化SampleContent的数据源更新逻辑

给SampleContent新增.onChange监听bundleID的变化,确保切换应用时自动重新加载对应的数据:

struct SampleContent: View {
    @Binding var bundleID: String
    @Environment(\.modelContext) private var modelContext
    @State var sampleData: SampleData?

    func loadData(bundleID: String) -> SampleData? {
        let filter = FetchDescriptor<SampleData>(predicate: #Predicate { $0.bundle == bundleID })
        do {
            if let sd = try modelContext.fetch(filter).first {
                return sd
            } else {
                let sd = SampleData(bundle: bundleID, data: "New Data: \(bundleID)")
                modelContext.insert(sd)
                return sd
            }
        } catch {
            return nil
        }
    }

    var body: some View {
        VStack {
            if let sd = sampleData {
                Text(sd.bundle)
                EditSampleData(sampleData: sd)
            }
        }
        .onAppear {
            sampleData = loadData(bundleID: bundleID)!
        }
        // bundleID变化时重新加载对应数据
        .onChange(of: bundleID) { newID in
            sampleData = loadData(bundleID: newID)!
        }
    }
}

3. 规避强制解包风险

在XBApp中获取前台应用时,改用可选绑定避免崩溃:

.onAppear {
    if let app = NSWorkspace.shared.frontmostApplication, let id = app.bundleIdentifier {
        bundleID = id
        print(bundleID)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 08:55:58