私はNSManagedObject
と呼ばれるEvent
をホストアプリと今日の拡張機能の間で共有しています。 (ターゲットメンバーシップでは、メインアプリとウィジェットの両方がチェックされます)。
ホストアプリとウィジェットは同じアプリグループ識別子を持ち、両方がData Model
(ターゲットメンバーシップでは、メインアプリとウィジェットの両方がチェックされます)。
Xcodeでウィジェットを起動(実行)すると、ホストアプリに既に保存されているすべてのアプリイベント(Event
)が表示されます。ただし、新しいイベントを追加すると、ホストアプリには表示されますが、today-widgetには表示されません。ウィジェットを再起動すると、以前はなかった最後のイベントを含むすべてのイベントが表示されます。
これは、イベントをフェッチするメソッドです。ウィジェットの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)")
}
}
}
}
問題は、メインアプリとアプリ拡張機能がiOSで2つの異なるプロセスとして機能することです。
CoreDataは、メインのアプリプロセス内でのみ通知を送信するNotificationCenter
と連携します。したがって、ここでプロセス間通知を送信する必要があります。
IOSでプロセス間通知を送信する1つのhidden方法は、UserDefaults
オブジェクトでKVOを使用することです。
NSUserDefaults.h
ヘッダーファイルAppleは、
/ *! NSUserDefaultsDidChangeNotificationは、現在のプロセス内でユーザーのデフォルトが変更されるたびにポストされますが、ユビキタスのデフォルトが変更されたとき、または外部のプロセスがデフォルトを変更したときはポストされません。 Key-Value監視を使用して、関心のある特定のキーのオブザーバーを登録すると、どこから更新されたかに関係なく、すべての更新が通知されます。 * /
これを指定すると、UserDefaults
の特定のキーでKVOを使用することにより、値の変更がアプリからエクステンションに、またはその逆に伝搬されると想定できます。
したがって、メインアプリでの変更ごとに、変更の現在のタイムスタンプを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")
}