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

问题描述

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

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

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

我该如何解决


TimerApp.swift(主机应用程序)

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(Widget 扩展)

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)
        }
    }
}

更新

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

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

*** 由于未捕获的异常“NSinvalidargumentexception”而终止应用程序,原因:“-[TimerEntity task]: 无法识别的选择器发送到实例 0x600003175400' 终止 未捕获的 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 (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,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 初始化时运行。您还需要在 NSFetchRequest 函数中执行此 getTimeline,以便更新您的 timerEntity


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

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

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

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

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