当 WidgetCenter.shared.reloadAllTimelines() 被调用时,WidgetKit 不会从 Core Data 获取更新的数据

Vic*_*lov 5 core-data swift cloudkit widgetkit swiftui

该应用程序使用 Core Data + CloudKit。当应用程序启动时,WidgetKit 会从核心数据中获取正确的条目,但是当我从主应用程序向核心数据添加新条目并调用WidgetCenter.shared.reloadTimelines()更新的条目时,不会获取到旧数据,并且旧数据会显示在小部件内。

主应用程序和小部件目标具有相同的应用程序组,两个目标都启用了 iCloud 功能。

当我WidgetCenter.shared.reloadTimelines()从主应用程序调用小部件重新加载(添加到小部件视图的计时器设置为零)但从核心数据获取时,不会返回新添加的条目。然后我重新启动应用程序并获取正确的条目。

为什么当 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.")
    }
}   
Run Code Online (Sandbox Code Playgroud)

这是核心数据的代码:

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])
  }
}
Run Code Online (Sandbox Code Playgroud)

Vic*_*lov 7

事实证明,我所要做的就是在进行获取之前将Core Data 的上下文更新为当前存储生成(在 WidgetKit 文件中的 getTimeline() 函数):

try? Storage.viewContext.setQueryGenerationFrom(.current)
Storage.viewContext.refreshAllObjects()
Run Code Online (Sandbox Code Playgroud)

请在此处阅读更多相关内容:https ://developer.apple.com/documentation/coredata/accessing_data_when_the_store_has_changed

  • 感谢您分享您的解决方案! (2认同)