How to ensure WidgetKit view shows correct results from @FetchRequest?

后端 未结 1 1623
天涯浪人
天涯浪人 2021-01-01 06:54

I have an app that uses Core Data with CloudKit. Changes are synced between devices. The main target has Background Modes capability with checked Remote notifications. Main

相关标签:
1条回答
  • 2021-01-01 07:36

    Widget views don't observe anything. They're just provided with TimelineEntry data. Which means @FetchRequest, @ObservedObject etc. will not work here.


    1. Enable remote notifications for your container:
    let container = NSPersistentContainer(name: "DataModel")
    let description = container.persistentStoreDescriptions.first
    description?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
    
    1. Update your CoreDataManager to observe remote notifications:
    class CoreDataManager {
        var itemCount: Int?
    
        private var observers = [NSObjectProtocol]()
    
        init() {
            fetchData()
            observers.append(
                NotificationCenter.default.addObserver(forName: .NSPersistentStoreRemoteChange, object: nil, queue: .main) { _ in
                    // make sure you don't call this too often - notifications may be posted in very short time frames
                    self.fetchData()
                }
            )
        }
    
        deinit {
            observers.forEach(NotificationCenter.default.removeObserver)
        }
    
        func fetchData() {
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Item")
    
            do {
                self.itemCount = try CoreDataStack.shared.managedObjectContext.count(for: fetchRequest)
                WidgetCenter.shared.reloadAllTimelines()
            } catch {
                print("Failed to fetch: \(error)")
            }
        }
    }
    
    1. Add another field in the Entry:
    struct SimpleEntry: TimelineEntry {
        let date: Date
        let itemCount: Int?
    }
    
    1. Use it all in the Provider:
    struct Provider: TimelineProvider {
        let coreDataManager = CoreDataManager()
    
        ...
    
        func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
            let entries = [
                SimpleEntry(date: Date(), itemCount: coreDataManager.itemCount),
            ]
    
            let timeline = Timeline(entries: entries, policy: .never)
            completion(timeline)
        }
    }
    
    1. Now you can display your entry in the view:
    struct WidgetExtEntryView: View {
        var entry: Provider.Entry
    
        var body: some View {
            VStack {
                Text(entry.date, style: .time)
                Text("Count: \(String(describing: entry.itemCount))")
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题