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

调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 02:18:11