本文介绍了如何确保WidgetKit视图显示来自@FetchRequest的正确结果?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个使用CoreKit和CloudKit的应用程序。更改在设备之间同步。主要目标具有后台模式功能以及选中的远程通知。主要目标和小部件目标都具有相同的应用程序组,并且都具有iCloud功能,服务设置为CloudKit,并且在选中的容器中具有相同的容器。

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 target and widget target both have the same App Group, and both have iCloud capability with Services set to CloudKit and same container in Containers checked.

我的目标是显示 actual SwiftUI WidgetKit视图中的核心数据条目。

My goal is to display actual Core Data entries in SwiftUI WidgetKit view.

我的小部件目标文件:

import WidgetKit
import SwiftUI
import CoreData

// MARK: For Core Data

public extension URL {
    /// Returns a URL for the given app group and database pointing to the sqlite database.
    static func storeURL(for appGroup: String, databaseName: String) -> URL {
        guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
            fatalError("Shared file container could not be created.")
        }
        
        return fileContainer.appendingPathComponent("\(databaseName).sqlite")
    }
}

var managedObjectContext: NSManagedObjectContext {
    return persistentContainer.viewContext
}

var workingContext: NSManagedObjectContext {
    let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
    context.parent = managedObjectContext
    return context
}

var persistentContainer: NSPersistentCloudKitContainer = {
    let container = NSPersistentCloudKitContainer(name: "Countdowns")
    
    let storeURL = URL.storeURL(for: "group.app-group-countdowns", databaseName: "Countdowns")
    let description = NSPersistentStoreDescription(url: storeURL)
    
    
    container.loadPersistentStores(completionHandler: { storeDescription, error in
        if let error = error as NSError? {
            print(error)
        }
    })
        
    container.viewContext.automaticallyMergesChangesFromParent = true
    container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
    
    return container
}()

// MARK: For Widget

struct Provider: TimelineProvider {
    var moc = managedObjectContext
    
    init(context : NSManagedObjectContext) {
        self.moc = context
    }
    
    func placeholder(in context: Context) -> SimpleEntry {
        return SimpleEntry(date: Date())
    }
    
    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date())
        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)
            entries.append(entry)
        }
        
        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
}


struct CountdownsWidgetEntryView : View {
    var entry: Provider.Entry
    
    @FetchRequest(entity: Countdown.entity(), sortDescriptors: []) var countdowns: FetchedResults<Countdown>
    
    var body: some View {
        return (
            VStack {
                ForEach(countdowns, id: \.self) { (memoryItem: Countdown) in
                    Text(memoryItem.title ?? "Default title")
                }.environment(\.managedObjectContext, managedObjectContext)
                Text(entry.date, style: .time)
            }
        )
    }
}

@main
struct CountdownsWidget: Widget {
    let kind: String = "CountdownsWidget"
    
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider(context: managedObjectContext)) { entry in
            CountdownsWidgetEntryView(entry: entry)
                .environment(\.managedObjectContext, managedObjectContext)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}

struct CountdownsWidget_Previews: PreviewProvider {
    static var previews: some View {
        CountdownsWidgetEntryView(entry: SimpleEntry(date: Date()))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}

但是我有一个问题:,假设我在主应用程序中有3条 Countdown 记录:

But I have a problem: let's say I have 3 Countdown records in the main app:

在开始小部件中视图在预览中显示了3条预期的记录(用于添加小部件的UI)。但是将小部件添加到主屏幕后,它不显示 Countdown 行,仅显示 entry.date,样式:.time 。当时间线条目更改时,行也不可见。我制作了一张图片来更好地说明这一点:

At the start widget view shows 3 records as expected in preview (UI for adding a widget). But after I add a widget to the home screen, it does not show Countdown rows, only entry.date, style: .time. When timeline entry changes, rows not visible, too. I made a picture to illustrate this better:

或:

在开始的窗口小部件视图中显示了3条记录,但是,大约一分钟后,如果我在主应用程序中删除或添加了 Countdown 条记录,则窗口小部件 still 显示初始3个值,但我希望它显示实际值的数量(以反映更改)。时间轴 entry.date,样式.time 的更改反映在小部件中,但不反映请求中的条目。

At the start widget view shows 3 records as expected, but after a minute or so, if I delete or add Countdown records in the main app, widget still shows initial 3 values, but I want it to show the actual number of values (to reflect changes). Timeline entry.date, style .time changes, reflected in the widget, but not entries from request.

有什么方法可以确保我的小部件显示正确的提取请求结果?谢谢。

Is there any way to ensure my widget shows correct fetch request results? Thanks.

推荐答案

窗口小部件视图不观察。它们只是提供了 TimelineEntry 数据。这意味着 @FetchRequest @ObservedObject 等在这里不起作用。

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


  1. 为容器启用远程通知:


let container = NSPersistentContainer(name: "DataModel")
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)



  1. 更新您的CoreDataManager以观察远程通知:


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. 添加其他 Entry 中的字段:

  1. Add another field in the Entry:


struct SimpleEntry: TimelineEntry {
    let date: Date
    let itemCount: Int?
}



  1. 在<$ c中全部使用$ c> Provider :

  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. 现在您可以在视图中显示您的条目:


struct WidgetExtEntryView: View {
    var entry: Provider.Entry

    var body: some View {
        VStack {
            Text(entry.date, style: .time)
            Text("Count: \(String(describing: entry.itemCount))")
        }
    }
}

这篇关于如何确保WidgetKit视图显示来自@FetchRequest的正确结果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-27 15:08