23

I am migrating an application from the deprecated Address Book Framework to the new Contacts Framework. The application utilizes ABAddressBookRegisterExternalChangeCallback to be notified when another application changes a contact.

I am unable to find equivalent functionality in the Contacts Framework. Apple documentation says to use the default notification center with the CNContactStoreDidChangeNotification notification:

The notification posted when changes occur in another CNContactStore.

Taking Apple's advice, my code looks like this:

NSNotificationCenter.defaultCenter().addObserver(
    self,
    selector: "contactsChanged:",
    name: CNContactStoreDidChangeNotification,
    object: nil)

However, I have found two problems with this approach:

  1. I am notified for all changes, including those made by my own application.
  2. Notifications are spurious - I receive many notifications for a single change.

If I log the debug description of the notification when the change was made within my app, I get something like this:

NSConcreteNotification 0x7d3370e0 {name = CNContactStoreDidChangeNotification; userInfo = {
    CNNotificationOriginationExternally = 1;
    CNNotificationSourcesKey =     (
    );
}}

And if the changes are made externally:

NSConcreteNotification 0x7bf7a690 {name = CNContactStoreDidChangeNotification; userInfo = {
    CNNotificationOriginationExternally = 1;
    CNNotificationSourcesKey =     (
    );
}}

As you can see, nothing obvious with which to distinguish them.

Can anyone tell me how to get the same behavior from the Contacts Framework as one can get from ABAddressBookRegisterExternalChangeCallback?

rickster
  • 124,678
  • 26
  • 272
  • 326
me--
  • 1,978
  • 1
  • 22
  • 42
  • self is referring to the class instance, where you are calling this method from. – ogres Mar 22 '16 at 10:03
  • @ogres sure, poor wording on my part. Basically, does passing in `self` achieve what I'm after (receiving only those changes from external apps)? For that to work, what _type_ would `self` be? – me-- Mar 22 '16 at 10:13
  • self could by any type, as long as it has addressBookDidChange: method declared. – ogres Mar 22 '16 at 10:18
  • OK, but what about my question regarding only external changes? – me-- Mar 22 '16 at 10:29
  • yes, you will receive notifications when it changes. – ogres Mar 22 '16 at 10:32
  • Yes, I do. But I receive them _even when it's my application making the change_. This is the point of my question: how do I receive them only for changes made by _other_ applications. – me-- Mar 23 '16 at 09:17
  • log notification and check if there is a difference between external and internal changes notification. – ogres Mar 23 '16 at 09:56
  • If documentation is right you should use only one CNContactStore in your application - then you should get only changes made by other apps. – DanielS Mar 25 '16 at 15:58
  • @DanielS that's what I'm doing. – me-- Mar 28 '16 at 01:09
  • 2
    Given that this is a new framework, it's entirely possible that this is a bug/oversight. Maybe file a Radar about it? – mszaro Mar 29 '16 at 01:16
  • What does your app's `NotificationCenter.default.post` code look like? Or, if this is fixed, can you post an answer (or select one) to explain? – leanne Feb 02 '17 at 23:08
  • I agree that this is a bug. I've worked around this issue by listening for `kABDatabaseChangedExternallyNotification` instead of `CNContactStoreDidChangeNotification`. If you look at `.userInfo` from the `kABDatabaseChangedExternallyNotification`, you'll see that one of the notification's `ABSenderProcessName`s is `AddressBookSourceSync`. If you update a contact from your phone, you'll only get the `AddressBookSourceSync` notification. – Beau Hartshorne Jan 17 '18 at 20:04

1 Answers1

2

First, I'd recommend filing a bug with Apple about the lack of a way to identify internal vs external changes in the API.

As a possible workaround, you could see if unregistering your observer before making a change and re-registering immediately afterward ensures that you miss all of your change notifications and still get all the external ones:

class ContactsThingy {

    var observer: NSObjectProtocol?
    let contacts = CNContactStore()

    func contactStoreDidChange(notification: NSNotification) {
        NSLog("%@", notification)
    }

    func registerObserver() {
        let center = NSNotificationCenter.defaultCenter()
        observer = center.addObserverForName(CNContactStoreDidChangeNotification, object: nil, queue: NSOperationQueue.currentQueue(), usingBlock: contactStoreDidChange)
    }

    func unregisterObserver() {
        guard let myObserver = observer else { return }
        let center = NSNotificationCenter.defaultCenter()
        center.removeObserver(myObserver)
    }

    func changeContacts(request: CNSaveRequest) {
        unregisterObserver() // stop watching for changes
        defer { registerObserver() } // start watching again after this change even if error
        try! contacts.executeSaveRequest(request)
    }
}
rickster
  • 124,678
  • 26
  • 272
  • 326