如何在宿主应用程序关闭时从 Widget iOS 14 中的 CoreData 重新加载获取数据

How to reload fetch data from CoreData in Widget iOS 14 when host App closes

我目前正在使用 SwiftUI 开发应用程序。

我想在 widget 中重新加载来自 CoreData 的数据,以便在每次关闭主机应用程序时更新更新的数据。

但在我的代码中,数据不会重新加载...

我该如何解决?


TimerApp.swift(主机APP)

import SwiftUI
import WidgetKit

@main
struct TimerApp: App {
    @Environment(\.scenePhase) private var scenePhase
    
    let persistenceController = PersistenceController.shared.managedObjectContext
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController)
                .onChange(of: scenePhase) { newScenePhase in
                    if newScenePhase == .inactive {
                        WidgetCenter.shared.reloadAllTimelines()
                    }
                }
        }
    }
}

TimerWidget.swift(插件扩展)

import WidgetKit
import SwiftUI
import CoreData

struct Provider: TimelineProvider {
    
    var moc = PersistenceController.shared.managedObjectContext
    var timerEntity:TimerEntity?
    
    init(context : NSManagedObjectContext) {
        self.moc = context
        let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
        
        do{
            let result = try moc.fetch(request)
            timerEntity = result.first
        }
        catch let error as NSError{
            print("Could not fetch.\(error.userInfo)")
        }
    }
    
    func placeholder(in context: Context) -> SimpleEntry {
        return SimpleEntry(date: Date(), timerEntity: timerEntity!)
    }
    
    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), timerEntity: timerEntity!)
        return completion(entry)
    }
    
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []
        
        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .minute, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, timerEntity: timerEntity!)
            entries.append(entry)
        }
        
        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
    let timerEntity:TimerEntity
}

struct TimerWidgetEntryView : View {
        
    var entry: Provider.Entry
    
    var body: some View {
        return (
            VStack{
                Text(entry.timerEntity.task ?? "")
            }
        )
    }
}

@main
struct TimerWidget: Widget {
    let kind: String = "TimerWidget"
    
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider(context: PersistenceController.shared.managedObjectContext)) { entry in
            TimerWidgetEntryView(entry: entry)
                .environment(\.managedObjectContext, PersistenceController.shared.managedObjectContext)
        }
    }
}

已更新

我更新了我的代码以在 getTimeline 函数和下面执行 NSFetchRequest

但是在我的代码中,当我构建这个项目时出现如下错误:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TimerEntity task]: unrecognized selector sent to instance 0x600003175400' terminating with uncaught exception of type NSException

struct Provider: TimelineProvider {
    
    var moc = PersistenceController.shared.managedObjectContext
    @State var timerEntity:TimerEntity?
    
    init(context : NSManagedObjectContext) {
        self.moc = context
        let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
        
        do{
            let result = try moc.fetch(request)
            timerEntity = result.first
        }
        catch let error as NSError{
            print("Could not fetch.\(error.userInfo)")
        }
    }
    
    func placeholder(in context: Context) -> SimpleEntry {
        return SimpleEntry(date: Date(), timerEntity: timerEntity ?? TimerEntity())
    }
    
    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), timerEntity: timerEntity!)
        return completion(entry)
    }
    
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []
        let currentDate = Date()
        
        let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
        do{
            let result = try moc.fetch(request)
            timerEntity = result.first
        }
        catch let error as NSError{
            print("Could not fetch.\(error.userInfo)")
        }
        
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .minute, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, timerEntity: timerEntity ?? TimerEntity())
            entries.append(entry)
        }
        
        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

Xcode:版本 12.0.1

iOS: 14.0

生命周期:SwiftUI 应用程序

负责取数据的代码只在init:

struct Provider: TimelineProvider {
    var moc = PersistenceController.shared.managedObjectContext
    var timerEntity:TimerEntity?
    
    init(context : NSManagedObjectContext) {
        self.moc = context
        let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
        
        do{
            let result = try moc.fetch(request)
            timerEntity = result.first
        }
        catch let error as NSError{
            print("Could not fetch.\(error.userInfo)")
        }
    }
    ...
}

只有在初始化Provider时才为运行。您还需要在 getTimeline 函数中执行此 NSFetchRequest,因此您的 timerEntity 已更新。


此外,如果您每次都执行 WidgetCenter.shared.reloadAllTimelines() scenePhase == .inactive,这可能太频繁了,您的 Widget 可能会停止更新。

inactive 每当您的应用程序从后台移动到前台(双向)时都会调用。

尝试改用 background - 只有当您的应用程序最小化或终止时才会调用它:

.onChange(of: scenePhase) { newScenePhase in
    if newScenePhase == .background {
        WidgetCenter.shared.reloadAllTimelines()
    }
}

注意:我建议在 WidgetCenter.shared.reloadAllTimelines() 周围添加一些保护措施,以确保不会 过于频繁地刷新它