What are the history-related Contacts framework class in iOS 13?
Asked Answered
I

1

7

New in iOS 13 are a bunch of history-related classes such as CNChangeHistoryEvent and CNChangeHistoryFetchRequest. There's no documentation and they are not mentioned in any WWDC 2019 video that I can find. What are they for and how do I use them?

Incur answered 12/10, 2019 at 19:46 Comment(2)
Did you ever make any progress with this API? It's April 2020, and Apple's documentation still says "No overview available"!Apthorp
Hah ... I'm pretty sure a big chunk of API doesn't leak like that, or at least if it does, it doesn't stick around for so many followup releases. I had a chance to speak to someone in the Contacts team (very briefly) at WWDC19, asking them about the new API and they said they'll have some documentation for it "soon". But (like with other teams at Apple) they haven't followed through.Apthorp
A
6

The API is supposed to run in a way where you can pass in a 'token' to the change history request, and it'll give you the contacts (or groups) that were added/deleted/updated since that token.

So far, I've only been able to 'run' the history fetch request, like this:

    CNChangeHistoryFetchRequest *fetchHistory = [[CNChangeHistoryFetchRequest alloc] init];
    fetchHistory.startingToken = [[NSUserDefaults standardUserDefaults] dataForKey:@"CNContactChangeHistoryToken"];

    NSError *error = nil;

    CNContactStore *store = [[CNContactStore alloc] init];
    CNFetchResult *fetchResult = [store enumeratorForChangeHistoryFetchRequest:fetchHistory error:&error];

    NSEnumerator *enumerator = [fetchResult value];
    id object;

    while ((object = [enumerator nextObject])) {
        // do something with object
        NSLog(@"change history enumerator object = %@", object);
        CNChangeHistoryEvent *historyEvent = (CNChangeHistoryEvent *) object;
        if ([historyEvent isKindOfClass:[CNChangeHistoryDropEverythingEvent class]]) {
            NSLog(@"change history - DROP EVERYTHING!");
            [historyEvent acceptEventVisitor: self];
        } else {
            if ([historyEvent isKindOfClass:[CNChangeHistoryAddContactEvent class]]) {
                CNChangeHistoryAddContactEvent *addContactEvent = (CNChangeHistoryAddContactEvent *) object;
                NSLog(@"change history - AddContact event container %@ - %@", addContactEvent.containerIdentifier, addContactEvent.contact);
            } else if ([historyEvent isKindOfClass:[CNChangeHistoryUpdateContactEvent class]]) {
                CNChangeHistoryUpdateContactEvent *updateContactEvent = (CNChangeHistoryUpdateContactEvent *) object;
                NSLog(@"change history - UpdateContact event - %@", updateContactEvent.contact);
            } else if ([historyEvent isKindOfClass:[CNChangeHistoryDeleteContactEvent class]]) {
                CNChangeHistoryDeleteContactEvent *deleteContactEvent = (CNChangeHistoryDeleteContactEvent *) object;
                NSLog(@"change history - DeleteContact event - %@", deleteContactEvent.contactIdentifier);
            }
        }
    }

The enumerate runs and it's always the 'CNChangeHistoryDropEverythingEvent' event, followed by 'Add Contact' and 'Add Group' events for the entire contacts list. This is because I can't find a way to fetch the current token anywhere. The 'fetchResult' object should have a currentHistoryToken but it is always nil; so is the CNContactStore's currentHistoryToken object. So I'm not sure where to get that from, so I can pass it into the startingToken next time.

Apthorp answered 19/4, 2020 at 0:43 Comment(6)
Were you able to get currentHistoryToken? There is no documentation on Apple for the change history and even the enumerate method is not available on Swift :(Snowblink
I filed a "Feedback" a while back, and Apple recently confirmed to me that they fixed this issue starting with iOS14.6 and above, where you get a valid currentHistoryToken from the system.Apthorp
Thank you that explains why I couldn't get it on the simulator (iOS 14.5). I tried it on a device (iOS 14.7) and I was able to get a token.Snowblink
@Snowblink Take a look at this comment on my post at this comment at Apple's Developer forums (developer.apple.com/forums/thread/696387) by "DTS Engineer". It has an apple logo next to it. I think he works for Apple. He/she says, "There is a current bug that causes the currentHistoryToken property to be nil in Swift. As such, use Objective-C to fetch change history data in your app." That was 8 months ago. In any case, did anybody figure out how to use this api?Dearing
For me, I can get the token correctly in Swift code, but the method to fetch the history is not available in Swift enumeratorForChangeHistoryFetchRequest:error. I had to wrap this in an objc class to use it in Swift code.Snowblink
This set me on the right path. It took me a while to figure out but fetchResult.currentHistoryToken need to be saved in the user default. https://mcmap.net/q/1623690/-how-would-i-check-if-a-cncontact-has-changed-since-the-last-time-my-ios-app-saved-it-in-the-contact-storeJameejamel

© 2022 - 2024 — McMap. All rights reserved.