#swift #core-data #swiftui #cloudkit #widgetkit
#swift #core-data #swiftui #cloudkit #widgetkit
Вопрос:
Приложение использует Core Data CloudKit. При запуске приложения WidgetKit извлекает правильные записи из Core Data, но когда я добавляю новые записи в Core Data из основного приложения и вызываю WidgetCenter.shared.reloadTimelines()
обновленные записи, они не извлекаются, и старые данные отображаются внутри виджета.
Основное целевое приложение и виджет имеют одну и ту же группу приложений, для обеих целей включена функция iCloud.
Когда я вызываю WidgetCenter.shared.reloadTimelines()
виджет из основного приложения, он перезагружается (таймер, добавленный в представление виджета, устанавливается равным нулю), но выборка из Core Data не возвращает вновь добавленные записи. Затем я перезапускаю приложение, и извлекаются правильные записи.
Почему он не извлекает правильные записи при вызове WidgetCenter.shared.reloadTimelines() и работает только при повторном запуске приложения?
Код виджета:
import WidgetKit
import SwiftUI
import CoreData
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> HabitsEntry {
HabitsEntry(date: Date(), habitsCount: 0)
}
func getSnapshot(in context: Context, completion: @escaping (HabitsEntry) -> ()) {
let entry = HabitsEntry(date: Date(), habitsCount: 0)
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
let managedObjectContext = Storage.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "DailyHabit")
let predicate = Storage.getCurrentDatePredicate()
request.predicate = predicate
var habits = 0
do { habits = try managedObjectContext.count(for: request) }
catch let error as NSError {print ("Could not fetch (error), (error.userInfo)")}
let entry = [HabitsEntry(date: Date(), habitsCount: habits)]
let timeline = Timeline(entries: entry, policy: .never)
completion(timeline)
}
}
struct HabitsEntry: TimelineEntry {
let date: Date
var habitsCount: Int
}
struct HabitsHomeWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
Text(entry.date, style: .timer)
Text("There are (entry.habitsCount) daily habits")
}
}
@main
struct HabitsHomeWidget: Widget {
let kind: String = "HabitsHomeWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
HabitsHomeWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
Вот код для Core Data:
import UIKit
import CoreData
import WidgetKit
class Storage {
static let shared = Storage()
static var persistentContainer: NSPersistentContainer {
return Storage.shared.persistentContainer
}
static var viewContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
lazy var persistentContainer: NSPersistentContainer = {
let container: NSPersistentContainer
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.appGroupName)!
let storeURL = containerURL.appendingPathComponent("NoRegrets.sqlite")
let description = NSPersistentStoreDescription(url: storeURL)
if isICloudContainerAvailable {
container = NSPersistentCloudKitContainer(name: Constants.persistentContainerName)
} else {
container = NSPersistentContainer(name: Constants.persistentContainerName)
}
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
description.shouldMigrateStoreAutomatically = true
description.shouldInferMappingModelAutomatically = true
let storeDescription = description
container.persistentStoreDescriptions = [storeDescription]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
return
}
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.automaticallyMergesChangesFromParent = true
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
print("Failed to pin viewContext to the current generation: (error)")
}
return container
}()
var isICloudContainerAvailable: Bool {
FileManager.default.ubiquityIdentityToken != nil
}
// MARK: - Core Data Saving support
func saveContext() {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
print("Saving error: (nserror)")
}
}
}
}
// MARK: - Helper methods
extension Storage {
func getCurrentUser() -> User? {
let fetchRequest = User.createFetchRequest()
fetchRequest.fetchLimit = 1
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "objectID", ascending: false)]
let user = try? Storage.viewContext.fetch(fetchRequest).first
return user
}
class func getCurrentDatePredicate() -> NSPredicate {
var calendar = Calendar.current
calendar.timeZone = NSTimeZone.local
let dateFrom = calendar.startOfDay(for: Date())
let dateTo = calendar.date(byAdding: .day, value: 1, to: dateFrom)
let fromPredicate = NSPredicate(format: "date >= %@", dateFrom as NSDate)
let toPredicate = NSPredicate(format: "date < %@", dateTo! as NSDate)
return NSCompoundPredicate(andPredicateWithSubpredicates: [fromPredicate, toPredicate])
}
}
Ответ №1:
Оказывается, все, что мне нужно было сделать, это обновить контекст Core Data до текущего поколения хранилища (в файле WidgetKit, функция getTimeline()) перед выполнением выборки:
try? Storage.viewContext.setQueryGenerationFrom(.current)
Storage.viewContext.refreshAllObjects()
Пожалуйста, прочитайте больше об этом здесь: https://developer.apple.com/documentation/coredata/accessing_data_when_the_store_has_changed
Комментарии:
1. Спасибо, что поделились своим решением!