gpt4 book ai didi

ios - 如何确保 WidgetKit View 显示来自 @FetchRequest 的正确结果?

转载 作者:行者123 更新时间:2023-12-04 01:13:51 25 4
gpt4 key购买 nike

我有一个将 Core Data 与 CloudKit 结合使用的应用程序。更改在设备之间同步。主要目标具有后台模式功能,并带有选中的远程通知。 Main target 和 widget target 都具有相同的应用程序组,并且都具有 iCloud 功能,服务设置为 CloudKit 并选中容器中的相同容器。

我的目标是在 SwiftUI WidgetKit View 中显示实际 Core Data 条目。

我的 WidgetKit 目标文件:

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 记录:

在开始时, WidgetKit View 按预期在预览中显示 3 条记录(用于添加 WidgetKit 的 UI)。但在我将 WidgetKit 添加到主屏幕后,它不显示 Countdown 行,仅显示 entry.date, style: .time。当时间线条目发生变化时,行也不可见。我做了一张图片来更好地说明这一点:

adding a widget

或者:

在开始时 WidgetKit View 按预期显示 3 条记录,但大约一分钟后,如果我在主应用程序中删除或添加 Countdown 记录, WidgetKit 仍然显示初始 3 个值,但我希望它显示值的实际数量(以反射(reflect)变化)。时间轴 entry.date, style .time 更改,反射(reflect)在 WidgetKit 中,但不是来自请求的条目。

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

最佳答案

WidgetKit View 不观察任何东西。它们只是提供了 TimelineEntry 数据。这意味着 @FetchRequest@ObservedObject 等在这里不起作用。


  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 中添加另一个字段:
struct SimpleEntry: TimelineEntry {
let date: Date
let itemCount: Int?
}
  1. 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. 现在您可以在 View 中显示您的条目:
struct WidgetExtEntryView: View {
var entry: Provider.Entry

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

关于ios - 如何确保 WidgetKit View 显示来自 @FetchRequest 的正确结果?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63991081/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com