调用reloadAllTimelines后SwiftUI Widget仍未刷新的问题求助
SwiftUI Widget 无法同步Core Data更新问题
问题场景与现象
- 开发SwiftUI Widget用于展示主应用Core Data数据,期望主应用数据库更新时Widget同步刷新
- 主应用设置页添加手动刷新按钮,点击后控制台输出Widget配置信息,但Widget未刷新,
getTimeline方法内的打印语句无输出 - 偶尔Widget会刷新,但主应用修改数据后无法同步更新,该问题在真机和模拟器中均存在
Widget代码
// // RemindMeWidget.swift // RemindMeWidget // // Created by Charel Felten on 05/07/2021. // import WidgetKit import SwiftUI import Intents import CoreData struct Provider: IntentTimelineProvider { func placeholder(in context: Context) -> SimpleEntry { SimpleEntry( date: Date(), configuration: ConfigurationIntent(), notes: Note.previewNotes, realFamily: context.family ) } func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { let entry = SimpleEntry( date: Date(), configuration: configuration, notes: Note.previewNotes, realFamily: context.family ) completion(entry) } func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { print("get timeline is called") var notes: [Note] = [] let containerURL = PersistenceController.containerURL let storeURL = containerURL.appendingPathComponent(PersistenceController.SQLiteStoreAppendix) let description = NSPersistentStoreDescription(url: storeURL) let container = NSPersistentCloudKitContainer(name: PersistenceController.containerName) container.persistentStoreDescriptions = [description] container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) let viewContext = PersistenceController.shared.container.viewContext let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Note") do { let result = try viewContext.fetch(request) // is there a nicer way to do it? if let tmp = result as? [Note] { notes = tmp } } catch { let nsError = error as NSError fatalError("Unresolved error \(nsError), \(nsError.userInfo)") } notes = notes.sorted { (lhs, rhs) in if lhs.int16priority == rhs.int16priority { return lhs.timestamp! > rhs.timestamp! } return lhs.int16priority > rhs.int16priority } let entry = SimpleEntry( date: Date(), configuration: configuration, notes: notes, realFamily: context.family ) print("hey") let timeline = Timeline(entries: [entry], policy: .atEnd) completion(timeline) } } struct SimpleEntry: TimelineEntry { let date: Date let configuration: ConfigurationIntent var notes: [Note] var realFamily: WidgetFamily } struct RemindMeWidgetEntryView : View { var entry: Provider.Entry var body: some View { WidgetView(notes: entry.notes, realFamily: entry.realFamily) } } @main struct RemindMeWidget: Widget { let kind: String = "RemindMeWidget" var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in RemindMeWidgetEntryView(entry: entry) } .configurationDisplayName("My Widget") .description("This is an example widget.") } } struct RemindMeWidget_Previews: PreviewProvider { static var previews: some View { RemindMeWidgetEntryView( entry: SimpleEntry( date: Date(), configuration: ConfigurationIntent(), notes: Note.previewNotes, realFamily: .systemSmall ) ).previewContext(WidgetPreviewContext(family: .systemSmall)) } }
主应用手动刷新按钮代码
Button("Reload Widget") { print("calling widgetcenter") WidgetCenter.shared.reloadAllTimelines() WidgetCenter.shared.getCurrentConfigurations({result in print(result)}) }
解决方案
1. 配置主应用与Widget共享App Group
- 打开Xcode,选中主应用target,进入
Signing & Capabilities,添加App Groups,创建或选择一个共享组(如group.com.yourdomain.RemindMe) - 给Widget target添加相同的App Group,确保两者能访问同一存储文件
2. 修正Core Data持久化容器路径
修改PersistenceController,使其使用App Group的容器URL,保证主应用和Widget访问同一数据库:
class PersistenceController { static let shared = PersistenceController() static let containerName = "RemindMe" static let SQLiteStoreAppendix = "RemindMe.sqlite" static var containerURL: URL { guard let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourdomain.RemindMe") else { fatalError("App Group容器URL未找到") } return groupURL } let container: NSPersistentCloudKitContainer init() { container = NSPersistentCloudKitContainer(name: PersistenceController.containerName) let storeURL = PersistenceController.containerURL.appendingPathComponent(PersistenceController.SQLiteStoreAppendix) let description = NSPersistentStoreDescription(url: storeURL) container.persistentStoreDescriptions = [description] container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("未解决错误 \(error), \(error.userInfo)") } }) } }
3. 修复Widget的getTimeline方法
复用共享的PersistenceController,使用performAndWait确保上下文操作同步完成,调整时间线策略为.never(依赖主动刷新):
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { print("get timeline is called") var notes: [Note] = [] let viewContext = PersistenceController.shared.container.viewContext viewContext.performAndWait { let request = NSFetchRequest<Note>(entityName: "Note") do { notes = try viewContext.fetch(request) notes = notes.sorted { (lhs, rhs) in guard let lhsPriority = lhs.int16priority, let rhsPriority = rhs.int16priority else { return lhs.timestamp ?? Date() > rhs.timestamp ?? Date() } if lhsPriority == rhsPriority { return lhs.timestamp ?? Date() > rhs.timestamp ?? Date() } return lhsPriority > rhsPriority } } catch { let nsError = error as NSError fatalError("未解决错误 \(nsError), \(nsError.userInfo)") } } let entry = SimpleEntry( date: Date(), configuration: configuration, notes: notes, realFamily: context.family ) print("hey") let timeline = Timeline(entries: [entry], policy: .never) completion(timeline) }
4. 主应用数据更新时主动触发Widget刷新
在主应用保存Core Data上下文后,调用WidgetCenter.shared.reloadAllTimelines():
// 示例:在保存context的代码块中添加 do { try viewContext.save() WidgetCenter.shared.reloadAllTimelines() } catch { // 处理保存错误 }
5. 确保手动刷新按钮在主线程调用
调整手动刷新按钮代码,保证WidgetCenter调用在主线程:
Button("Reload Widget") { print("calling widgetcenter") DispatchQueue.main.async { WidgetCenter.shared.reloadAllTimelines() WidgetCenter.shared.getCurrentConfigurations { result in print(result) } } }
内容的提问来源于stack exchange,提问作者charelf
相关产品推荐
相关产品推荐

