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

WidgetKit中@ObservableObject无法更新视图问题求助

SwiftUI数据模型与WidgetKit协同工作问题解决

你的核心问题有两个:

  • 异步加载时序错误:当前Widget的getTimeline方法中,异步调用JourneyBook.load后,没有等待回调完成就直接生成Timeline并调用completion,导致entries还没被填充就返回了,所以Widget拿到的是空数据。
  • 跨进程状态不共享:Widget和主App是独立的进程,主App中的ObservableObject状态无法直接同步到Widget,需要通过共享存储+主动刷新的方式实现数据同步。

1. 修复Widget异步加载的时序问题

修改Provider中的getTimeline方法,确保在JourneyBook.load的回调内部生成Timeline并完成回调:

struct Provider: IntentTimelineProvider {
    public typealias Entry = SimpleEntry

    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> ()) {
        let entryDate = Calendar.current.date(byAdding: .minute, value: 1, to: Date())!

        JourneyBook.load { result in
            var entries: [SimpleEntry] = []
            switch result {
            case .failure(let error):
                // 避免用fatalError,Widget进程崩溃会导致无法显示,建议用空数组或默认值
                print(error.localizedDescription)
                entries.append(SimpleEntry(date: entryDate, configuration: configuration, journeys: []))
            case .success(let journey):
                entries.append(SimpleEntry(date: entryDate, configuration: configuration, journeys: journey))
            }
            // 必须在异步回调内部生成Timeline并触发完成
            let timeline = Timeline(entries: entries, policy: .atEnd)
            completion(timeline)
        }
    }

    // Widget要求必须实现的方法,补充完整
    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), configuration: configuration, journeys: [])
        completion(entry)
    }

    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), configuration: ConfigurationIntent(), journeys: [])
    }
}

2. 实现主App与Widget的数据同步

因为Widget和主App是独立进程,主App中的JourneyBook更新不会自动通知Widget,需要做以下两步:

步骤1:使用App Group共享数据

确保JourneyBook的数据存储在App Group容器中(比如UserDefaults),让主App和Widget能访问同一数据源:

class JourneyBook: ObservableObject {
    @Published var journeys: [Journey] = []

    // 替换为你自己的App Group标识符,需在Xcode Capabilities中开启并配置
    private static let appGroupID = "group.com.yourcompany.Trainly"
    private static let sharedDefaults = UserDefaults(suiteName: appGroupID)!

    static func load(completion: @escaping (Result<[Journey], Error>) -> Void) {
        guard let data = sharedDefaults.data(forKey: "savedJourneys") else {
            completion(.success([]))
            return
        }
        do {
            let journeys = try JSONDecoder().decode([Journey].self, from: data)
            completion(.success(journeys))
        } catch {
            completion(.failure(error))
        }
    }

    // 主App数据更新时调用此方法保存到共享存储
    func saveJourneys() {
        do {
            let data = try JSONEncoder().encode(journeys)
            Self.sharedDefaults.set(data, forKey: "savedJourneys")
        } catch {
            print("保存数据失败:\(error.localizedDescription)")
        }
    }
}

步骤2:主App数据更新时主动刷新Widget

在主App中,当journeys发生变化时,调用WidgetCenter触发Widget重新加载数据:

@main
struct TrainlyApp: App {
    @StateObject var journeyBook = JourneyBook()

    var body: some Scene {
        WindowGroup {
            ContentView(journeys: $journeyBook.journeys)
                .onAppear {
                    JourneyBook.load { result in
                        switch result {
                        case .failure(let error):
                            print(error.localizedDescription)
                        case .success(let journey):
                            journeyBook.journeys = journey
                        }
                    }
                }
                .onChange(of: journeyBook.journeys) { _ in
                    // 保存数据并通知Widget刷新
                    journeyBook.saveJourneys()
                    WidgetCenter.shared.reloadAllTimelines()
                }
        }
    }
}

3. 关键注意事项

  • Widget视图仅基于TimelineEntry渲染,不要在Widget中使用@StateObject/@ObservedObject/@EnvironmentObject,所有数据必须通过Entry传递。
  • Widget进程会被系统频繁挂起/终止,不能依赖内存状态,每次加载都要从共享存储获取最新数据。
  • 避免在Widget中使用fatalError,否则会导致Widget崩溃无法显示,优先用空数据或默认值处理错误场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 04:15:39