Notification Extension Access Core Data

Notification extension access Core Data

Add your Core Data model to your target. In the inspector on the right, when you have your model open, enable it for your extension as well.

If you need to access the same store file, make sure to save it in a data container, shared between your app and extensions.

Can an application tune in to Core Data changes made by an extension?

I found a solution to the problem I described after being pointed towards using notification by @CerlinBoss. It is possible to send a notification from the extension to the application (or vice versa). This can be done in iOS using a Darwin notification center. The limitation however is that you can't use the notification to send custom data to your application.

After reading many articles I decided that I'd avoid making changes to the Core Data database from two different processes and using multiple managed contexts. Instead, I queue the data I need to communicate to the application inside a key in the UserDefaults and once the application is notified of the changes, I'd dequeue them and update the Core Data context.

Common Code

Swift 4.1

import os
import Foundation

open class UserDefaultsManager {

// MARK: - Properties

static let applicationGroupName = "group.com.organization.Application"

// MARK: - Alert Queue Functions

public static func queue(notification: [AnyHashable : Any]) {
guard let userDefaults = UserDefaults(suiteName: applicationGroupName) else {
return
}
// Retrieve the already queued notifications.
var alerts = [[AnyHashable : Any]]()
if let data = userDefaults.data(forKey: "Notifications"),
let items = NSKeyedUnarchiver.unarchiveObject(with: data) as? [[AnyHashable : Any]] {
alerts.append(contentsOf: items)
}
// Add the new notification to the queue.
alerts.append(notification)
// Re-archive the new queue.
let data = NSKeyedArchiver.archivedData(withRootObject: alerts)
userDefaults.set(data, forKey: "Notifications")
}

public static func dequeue() -> [[AnyHashable : Any]] {
var notifications = [[AnyHashable : Any]]()
// Retrieve the queued notifications.
if let userDefaults = UserDefaults(suiteName: applicationGroupName),
let data = userDefaults.data(forKey: "Notifications"),
let items = NSKeyedUnarchiver.unarchiveObject(with: data) as? [[AnyHashable : Any]] {
notifications.append(contentsOf: items)
// Remove the dequeued notifications from the archive.
userDefaults.removeObject(forKey: "Notifications")
}
return notifications
}
}

Extension:

Swift 4.1

  override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
os_log("New notification received! [%{public}@]", bestAttemptContent.body)
// Modify the notification content here...
// Queue the notification and notify the application to process it
UserDefaultsManager.queue(notification: bestAttemptContent.userInfo)
notifyApplication()
contentHandler(bestAttemptContent)
}
}

func notifyApplication() {
let name: CFNotificationName = CFNotificationName.init("mutableNotificationReceived" as CFString)
if let center = CFNotificationCenterGetDarwinNotifyCenter() {
CFNotificationCenterPostNotification(center, name, nil, nil, true)
os_log("Application notified!")
}
}

Application:

Swift 4.1

  // Subscribe to the mutableNotificationReceived notifications from the extension.
if let center = CFNotificationCenterGetDarwinNotifyCenter() {
let name = "mutableNotificationReceived" as CFString
let suspensionBehavior = CFNotificationSuspensionBehavior.deliverImmediately
CFNotificationCenterAddObserver(center, nil, mutableNotificationReceivedCallback, name, nil, suspensionBehavior)
}

let mutableNotificationReceivedCallback: CFNotificationCallback = { center, observer, name, object, userInfo in
let notifications = UserDefaultsManager.dequeue()
for notification in notifications {
// Update your Core Data contexts from here...
}
print("Processed \(notifications.count) dequeued notifications.")
}

How to access CoreData model in today extension (iOS)

What you really want is to access your persistent store (most likely a SQLite database).
In order to achieve that, you need to configure App Groups and make sure that your host app configures the Core Data stack using your shared container (so your store is accessible in extension as well).
Something like:

    NSString *containerPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:YOUR_SECURITY_APP_GROUP].path;
NSString *sqlitePath = [NSString stringWithFormat:@"%@/%@", containerPath, @"database.sqlite"];

Then in your extension just create persistent store coordinator with managed object contexts using database in shared container.
You can share your model (.momd) and managed object subclasses with extension just by making sure they are included in extension target as well.

Edit:

To add your model and managed object subclasses:

1. Make sure you have your app and extension targets

  1. Make sure you have your app and extension targets

    2. Click on your model file, and select both targets under 'Target Membership' on right-hand panel

  2. Click on your model file, and select both targets under 'Target Membership' on right-hand panel

    3. Repeat the same with all your managed object subclasses

  3. Repeat the same with all your managed object subclasses

iOS today extension with core data

You should subclass NSPersistentCloudKitContainer like below, returning the App Group URL for defaultDirectoryURL(). Then in your CoreDataStack, use let container = GroupedPersistentCloudKitContainer(name: "SchoolCompanion"). Also remove, your call to addToAppGroup(...). You will need to instantiate the GroupedPersistentCloudKitContainer in both the App and the Extension, you will also need to make sure the GroupedPersistentCloudKitContainer is linked to both Targets.

class GroupedPersistentCloudKitContainer: NSPersistentCloudKitContainer {

enum URLStrings: String {
case group = "group.com.yourCompany.yourApp"
}

override class func defaultDirectoryURL() -> URL {
let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: URLStrings.group.rawValue)

if !FileManager.default.fileExists(atPath: url!.path) {
try? FileManager.default.createDirectory(at: url!, withIntermediateDirectories: true, attributes: nil)
}
return url!
}
...
}


Related Topics



Leave a reply



Submit