bugfix> swift > 投稿

私は現在、SwiftUIを使用してアプリケーションを開発しています。

からデータをリロードしたい CoreDatawidget ホストアプリを閉じるたびに新しいデータを更新します。

しかし、私のコードでは、データはリロードされません...

どうすればそれを解決できますか?


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


更新しました

実行するようにコードを更新しました NSFetchRequest の中に getTimeline 以下と同様に機能します。

しかし、私のコードでは、このプロジェクトをビルドすると、次のようなエラーが発生します。

*** 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アプリ

回答 1 件
  • データのフェッチを担当するコードは 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 頻度が高すぎて、ウィジェットの更新が停止する可能性があります。

    inactive アプリがバックグラウンドからフォアグラウンドに(両方向に)移動するたびに呼び出されます。

    使ってみてください background 代わりに-これは、アプリが最小化または強制終了された場合にのみ呼び出されます。

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

    注:いくつかのセーフガードを追加することをお勧めします WidgetCenter.shared.reloadAllTimelines() 更新しないように頻繁すぎる

あなたの答え