WidgetKit не извлекает обновленные данные из основных данных при вызове WidgetCenter.shared.reloadAllTimelines()

#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. Спасибо, что поделились своим решением!