iOS - Уведомление сегодня о расширении основных данных в основном приложении. - PullRequest
5 голосов
/ 07 февраля 2020

У меня есть NSManagedObject, называемый Event, который совместно используется хост-приложением и расширением сегодня. (В Target Membership проверяются и основное приложение, и виджет).

Хост-приложение и виджет имеют одинаковый идентификатор группы приложений и оба совместно используют Data Model (В целевом членстве проверяются и основное приложение, и виджет).

Когда я запускаю (запускаю ) виджет в Xcode, он показывает все события приложения (Event), которые уже сохранены в главном приложении. Однако, когда я добавляю новое событие, оно появляется в главном приложении, но НЕ в сегодняшнем виджете. Если я перезапущу виджет, будут показаны все события, включая последнее событие, которое ранее не было.

Это метод, который извлекает события. Это определено в TodayViewController виджета.

private  func fetchEvents(date: Date) {
    let predicates = NSCompoundPredicate(andPredicateWithSubpredicates: [
        NSPredicate(format: "date = %@",Date().startOfDay as CVarArg),
        NSPredicate(format: "startTime >= %@", Date() as CVarArg)
    ])
    if let ev  = try? TPEvent.fetchAll(predicates: predicates, in: persistentManager.context) {
        events = ev
    }
} 

Это событие вызывается в viewWillAppear и widgetPerformUpdate.

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    fetchEvents(date: Date())
    self.tableView.reloadData()
}


func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
    self.fetchEvents(date: Date() )
    self.tableView.reloadData()
    completionHandler(NCUpdateResult.newData)
}

persistentManaged.context равно PersistentManager.shared.context (см. Код ниже).

Кстати, оба вышеуказанных метода вызываются при просмотре сегодняшнего виджета. У меня много времени, чтобы разобраться с этой проблемой, но я не смог этого сделать.

В чем может быть проблема и как ее исправить?

Пожалуйста, оставьте комментарий, если вам нужна дополнительная информация или у вас есть вопрос.

Обновление

У меня есть синглтон PersistentManager. Используйте viewContext как в главном приложении, так и в виджете.

 public final class PersistentManager {

    init() {}

    public static let shared = PersistentManager()

    public lazy var persistentContainer: NSPersistentContainer = {

        let container = NSPersistentCloudKitContainer(name: "Event")

        guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.event.data") else {
            fatalError("Shared file container could not be created.")
        }

        let storeURL = fileContainer.appendingPathComponent("Event.sqlite")
        let storeDescription = NSPersistentStoreDescription(url: storeURL)
        container.persistentStoreDescriptions = [storeDescription]

        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })

        container.viewContext.automaticallyMergesChangesFromParent = true

        do {
            try container.viewContext.setQueryGenerationFrom(.current)
        } catch {
            fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
        }

        return container
    }()

    public lazy var context = persistentContainer.viewContext

    // MARK: - Core Data Saving support
    public  func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {

                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }
}

1 Ответ

1 голос
/ 12 февраля 2020

Проблема заключается в том, что основное приложение и расширение приложения работают как два разных процесса на iOS.

CoreData работает с NotificationCenter, который отправляет уведомления только в рамках основного процесса приложения. Таким образом, вы должны отправить уведомление о межпроцессном режиме здесь.

Один скрытый способ отправить уведомление о межпроцессном режиме на iOS - это использовать KVO для объекта UserDefaults.

В заголовочном файле NSUserDefaults.h Apple заявляет, что

/ *! NSUserDefaultsDidChangeNotification публикуется всякий раз, когда какие-либо пользовательские значения по умолчанию изменяются в текущем процессе, но не публикуется при изменении вездесущих значений по умолчанию или когда внешний процесс меняет параметры по умолчанию. Использование наблюдения значения ключа для регистрации наблюдателей для указанных ключей c будет информировать вас обо всех обновлениях, независимо от того, откуда они. * /

Имея это, можно предположить, что при использовании KVO для определенного ключа UserDefaults изменение значения будет распространяться от приложения к расширению и наоборот.

Таким образом, подход может заключаться в том, что при каждом изменении в основном приложении вы сохраняете текущую метку времени изменения в UserDefaults:

/// When the change is made in the main app:
let defaults = UserDefaults(suiteName: "group.<your bundle id>")
defaults["LastChangeTimestamp"] = Date()
defaults.synchronize()

В расширении приложения:

let defaults = UserDefaults(suiteName: "group.<your bundle id>")

func subscribeForChangesObservation() {
    defaults?.addObserver(self, forKeyPath: "LastChangeTimestamp", options: [.new, .initial], context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    // Process your changes here.
}

deinit {
    defaults?.removeObserver(self, forKeyPath: "LastChangeTimestamp")
}
...