Have you figured this out?
I believe this can be done. I have an app from Intuit that always tracks me. My iPhone at times tells me that app is tracking my location and asks me if I want to turn that always tracking feature off.
Post
Replies
Boosts
Views
Activity
If anyone is interested,
I found this webpage: https://mackuba.eu/2019/12/03/new-stuff-from-wwdc-2019/#comment1
I asked this question:
Could you be specific about where during WWDC 2019 they said something about CNChangeHistoryFetchRequest and CNChangeHistoryEvent for the Contacts Framework?
I got this answer:
@ShinehahGnolaum Hey! These lists are combined from the things mentioned in the talks, but also from looking through the documentation e.g. at https://developer.apple.com/ios/, release notes, and just simply API reference (I use this page http://codeworkshop.net/objc-diff/sdkdiffs/ to see what new classes/methods were added in some interesting places). So some things listed (a large part of them actually) might not have been mentioned anywhere in the talks and I've just found them elsewhere. It's not necessarily "things mentioned at WWDC", but "things released in the betas at WWDC".
It looks like these two you've mentioned are things like that, because I don't see them anywhere in any talk notes or transcriptions. Which is unfortunate, because I see they still don't have a proper documentation… which is probably why you're asking. I think the only piece of information that can help you somewhat is the ObjC header CNChangeHistoryFetchRequest.h in the SDK - you can access it through the "Open Quickly" (Cmd+Shift+O) in Xcode. There are some code comments there that aren't rendered in the web documentation. Not much in CNChangeHistoryEvent.h though.
I finally came back to this question.
Again, I'm trying to duplicate the Objective-C code on this post on stackoverflow to Swift.
Here's the Objective-C code from the post:
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);
}
}
}
Here's my Swift code, as far as I got:
public func tryChangeHistoryFetchRequest() {
let fetchHistory = CNChangeHistoryFetchRequest()
let store = CNContactStore()
print("*****")
let fetchResult: CNFetchResult<NSEnumerator<CNChangeHistoryEvent>> = fetchHistory
}
// Enumerate through each visit function and check if I get anything back from fetch request.
class ChangeHistoryEventVisitor: NSEnumerator, CNChangeHistoryEventVisitor {
override func nextObject() -> Any? {
let changeHistoryDropEverythingEvent = CNChangeHistoryDropEverythingEvent()
return {}
}
func visit(_ event: CNChangeHistoryDropEverythingEvent) {
print("CNChangeHistoryDropEverythingEvent")
}
func visit(_ event: CNChangeHistoryAddContactEvent) {
}
func visit(_ event: CNChangeHistoryUpdateContactEvent) {
}
func visit(_ event: CNChangeHistoryDeleteContactEvent) {
}
}
public func tryChangeHistoryEventVisitor() {
print("*****")
let changeHistoryEventVisitor = ChangeHistoryEventVisitor()
print("*****")
let changeHistoryDropEverythingEvent = CNChangeHistoryDropEverythingEvent()
changeHistoryEventVisitor.visit(changeHistoryDropEverythingEvent)
}
class ChangeHistoryAddContactEvent: CNChangeHistoryAddContactEvent {
}
class CNChangeHistoryFetchResult: CNFetchResult<NSEnumerator<CNChangeHistoryEvent>> {
}
Hi. Did you ever figure this out? I found this thread at discussions.apple.com. It seems to say that when an iOS device is powered down and then powered on again, whatever app that was in the foreground when the device was turned off will show in the foreground again when it's powered on. Perhaps that fact opens up some options. I would like to know if anyone has found any leads to consider that might lead to a solution.
Anyone figure this out?
Did you ever find an answer to this problem?
That only fetches the contacts in the default container. Is there a way to fetch all the contacts in the entire store using CNContactStore instance method enumerateContacts(with:usingBlock:)?
I am resuming this thread because I have the same question as this one that I posted. I did a search on the Internet to answer a question and I found this old post that I posted.
I think the problem is I don’t understand what you’re referring to when you say “console”. Is that an app on macOS? Wouldn’t I be able to get this information if the user installed the app using TestFlight? I think there’s things I haven’t thought through I don’t understand, so my question doesn’t really make sense.
@OOPer The code still doesn't show formatted right in the comment. What do you suggest? I deleted the code in the comment above because it wasn't showing it formatted well.
I put what code I've tried in my post of a similar question on stackoverflow. They are formatted well in that post.
This is the only code I've found using CNFetchResult: stackoverflow. It's the same post I put in my comment earlier. It's in Objective-C because it uses a function of CNContactStore that is only available in Objective-C.
UPDATE: I deleted the code I put in this comment because I wasn't able to format it in any way worth trying to follow.
Here is my identical post on stackoverflow: iOS Contacts Framework Change History Data using Swift and Xcode 13. I just updated that post with what code I've tried to make.
There is a post on stackoverflow similar to this, that had an answer with a code sample in Objective-C that effectively used CNFetchResult with CNContactStore enumeratorForChangeHistoryFetchRequest, but that function is only available for Objective-C.
I just now sent a question to Apple Developer Support.
I found somewhere someone saying they went to a WWDC and asked about this to someone from Apple and the person from Apple told him Apple will be supplying documentation in the future. I don't know how long ago that story happened.
Do you know why there isn't current documentation on this?
@OOPer. I tried to write code in a post or a comment, and it wouldn't recognize a carriage-return, so the code shows as a long line of string that wraps at the end of each line in the text box, so I decided not to include the code. I tried also using the inline code feature, but that didn't seem to help.
I will try again.
I want to convert this code in the answer to this stackoverflow post. I got as far as the line that creates an instance of CNFetchResult -- the fifth line not counting the spaces. I didn't know how to translate that to Swift.
The sample iOS Swift project ManagingContacts found in Apple's Documentation Archive, uses both enumerateContacts(with:usingBlock:) and unifiedContact(withIdentifier:keysToFetch:). Understanding why they choose to use each when they do may help answer the question.
The name "unifiedContacts" suggests it only fetches unified contacts, and does not fetch non-unified contacts that otherwise would meet the specified criteria, but it doesn't make sense to me why there would ever be a need for it or why it is used in the said project when it is used.
I noticed that at https://developer.apple.com/download/all/, there is a button at the bottom of the list that says "View More". I didn't see that before. It was easy to miss. When I continue to click on that button, I eventually find Xcode 10.1.