SwiftUI本地变量初始化异常?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
相关产品推荐
相关产品推荐

