CloudKit subscription with "CONTAINS" predicate - ios

I am trying to setup a CloudKit subscription based on testing membership in an array.
The code I'm using to create the subscription is as follows:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"users CONTAINS %#", userID];
CKSubscription *itemSubscription = [[CKSubscription alloc] initWithRecordType:#"foo"
predicate:predicate
options:CKSubscriptionOptionsFiresOnRecordCreation|CKSubscriptionOptionsFiresOnRecordUpdate];
CKNotificationInfo *notificationInfo = [[CKNotificationInfo alloc] init];
[notificationInfo setAlertLocalizationKey:#"Record notification"];
[notificationInfo setShouldBadge:YES];
[itemSubscription setNotificationInfo:notificationInfo];
[database saveSubscription:itemSubscription completionHandler:^(CKSubscription *subscription, NSError *error) {
NSLog(#"Error: %#, Subscription: %#", error, subscription);
}];
The log shows that the subscription is created successfully, however when I test by adding or changing a record via the CloudKit admin console I never get a notification on device.
I am able to receive notifications for subscriptions with other kinds of predicates (I've tested with a simple true predicate, and one that tests equality against a string field), so I know I have the notification code setup correctly.
I've also verified that my predicate listed above works when used in a fetch records query, so I know the predicate is setup correctly for the record type I have in CloudKit.
Has anyone been able to get subscription notifications with a predicate that tests for membership in an array?

After playing around, I found that unless I set notificationInfo.alertbody the subscription never fired.
So try setting it to "Test" first, then after set it to "". I found it continued to work after I set it to a blank string (though I didn't try setting it blank to start with), and as a blank string it doesn't show a notification, but my handler still gets called, which was what I was after.
With iOS 8.1.1 / Xcode 6.1.1

Related

EKEventStore will not return Exchange Calendar items by external or local identifier

I am using the EKEventStore API to save events to the default calendar using the following:
EKEventStore - saveEvent:span:commit:error:.
Once the event is saved, I store the externalID and localID in my database for future reference using the following:
externalID = [myEvent calendarItemExternalIdentifier]; and
localID = [myEvent eventIdentifier].
The problem I am having is that when I then go back to try and retrieve the event using the following:
[[eventStore calendarItemsWithExternalIdentifier:externalID] firstObject]
OR
[eventStore eventWithIdentifier:localID],
iOS is not able to find my event.
If I run the exact same code, but have my default calendar set to an iCloud calendar, however, everything works correctly.
But if the default calendar is an Exchange calendar, I am getting the following error message:
"Error getting calendar item with UUID [insert externalID here]: Error Domain=EKCADErrorDomain Code=1010 "(null)""
Has anyone encountered this issue?
I have had this code deployed for over 2 years now and users recently reported they weren't able to open appointments created on Exchange calendars. Not sure what changed or when, but I have tested this on iOS 10 and 11, and both have the issue.
Any insight would be greatly appreciated,
Sincerely,
~Arash
Unfortunately not... we were able to work around this issue by including an internal reference ID in the notes of the event. Then, if both the externalEventID and eventID failed to return the event, which is our issue with Exchange, then we fall back to use the start and end date in an EKEventSearchCallback to look for our event.
Basically, it ends up looking like this:
EKEventSearchCallback searchBlock = ^(EKEvent *event, BOOL *stop)
{
if([MyCustomClass event:event
notesMatchesKnownValueDict:knownValueDict])
{
foundEvent = event;
*stop = YES;
}
};
NSPredicate *predicate = [eventStore predicateForEventsWithStartDate:startDate
endDate:endDate
calendars:nil];
[eventStore enumerateEventsMatchingPredicate:predicate usingBlock:searchBlock];
Another solution might be waiting for the event to sync with Exchange. For example, I save the event and keep track of the ID assigned to it. Have a loop check every few seconds for a change. When the change happens, it's on the exchange server and you're good to reference that in your DB.
Try it out, let me know if it works!

cloudkit notifications for a specific field within a record

I have cloudkit notifications working. When someone changes the record, the subscribers are notified. My subscription definition looks like:
NSPredicate *searchConditions = [NSPredicate predicateWithFormat:#"%K = %#", CLOUDKIT_PUBLIC_ID_GUID, theCloudGUID];
int subscriptionOptions = CKSubscriptionOptionsFiresOnRecordUpdate | CKSubscriptionOptionsFiresOnRecordDeletion;
CKSubscription *publicSubscription = [[CKSubscription alloc] initWithRecordType:CLOUDKIT_RECORDNAME_PUBLIC_ID
predicate:searchConditions
options:subscriptionOptions];
CKNotificationInfo *notificationInfo = [CKNotificationInfo new];
notificationInfo.alertLocalizationKey = CLOUDKIT_NOTIFICATION_PUBLIC;
notificationInfo.shouldBadge = NO;
notificationInfo.alertBody = CLOUDKIT_NOTIFICATIONBODY_PUBLIC;
publicSubscription.notificationInfo = notificationInfo;
[publicDatabase saveSubscription:publicSubscription completionHandler:^(CKSubscription * _Nullable subscription, NSError * _Nullable error)
{
//error handling
}
The thing is, there are multiple fields in this record. I only want to alert the subscriber when one specific field changes.
When creating the subscription, is there a way to set the search predicate to detect a change in a specific field? I read through the various Predicate docs, but didn't see this specifically mentioned.
Or, when receiving the notification, is there a way to see which fields changed? In didReceiveRemoteNotification I tried:
CKQueryNotification *queryNotification = [CKQueryNotification notificationFromRemoteNotificationDictionary:userInfo];
But queryNotification.recordFields is null.
As a worst case, I have considered breaking the specific field out into it's own record, but then I have the overhead of maintaining more records tied together by a common GUID. I was hoping to keep this more compact.
Your question is aging a bit, so maybe you already figured this out, but using the desiredKeys property may help: https://developer.apple.com/documentation/cloudkit/cknotificationinfo/1514931-desiredkeys
If these notifications are silent pushes, you could look at the payload to see if certain keys changed and have your app react accordingly.
If these are push (visible-to-the-user) notifications, I don't think you can push based on the key changing per se. You could set an NSPredicate on your CKQuerySubscription if you were testing how the value changed (is it equal to this or not equal to that, etc.), but I'm not sure about it being triggered for any change at all.

CKSubscription to creatorUserRecordID never arrives

I create CKSubscription using the following code:
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:#"(creatorUserRecordID == %#)", self.myRecordId];
CKSubscription *subscription2 = [[CKSubscription alloc]
initWithRecordType:#"Message"
predicate:predicate2
options:CKSubscriptionOptionsFiresOnRecordCreation];
where self.myRecordId is CKRecordID of the current logged user. I do this because I would like to receive notification when I create a new object (of type Message) but the problem is that notification never arrives. Anyone has idea why?
You should complete the missing steps described in the Quick Start Guide namely set the notification object, assign it to the subscription, and assign the subscription to the public database:
Please note the verification steps on the original source.

CloudKit CKSubscription, subscriptionID

I am trying to study CloudKit and I saw this. https://github.com/ghvillasboas/CloudKitTest
After I have followed that instruction, I can run app and save, fetch data.
However, I saw this code
CKSubscription *subscription = [[CKSubscription alloc]
initWithRecordType:GVCloudKitRecordType predicate:[NSPredicate predicateWithFormat:#"TRUEPREDICATE"] subscriptionID:GVCloudKitSubscriptionId options:CKSubscriptionOptionsFiresOnRecordCreation];
GVCloudKitSubscriptionId = br.com.cocoaheads.cloudkittest.newHeroSubscription for that one. But, for me, how can I create subscriptionID? May I know from where I can register that or get that?
You can define your own subscriptionID. Just remember that ID so that you can also unregister. Create a logical ID. Something like "all" if you are using a true predicate.

No CoreData Fetched Results in Nordic Countries

Frankly, I'm a little embarrassed to even ask this question for the perceived absurdity and without being able to personally reproduce it, but...
In my iOS 7 application, I've received many complaints from users that certain portions of the app "don't work." By "don't work," they mean that a UITableView being populated by a NSFetchRequestDelegate's methods has zero rows. The only thing in common about the error reports are the specific NSFetchRequests that return zero objects and the physical location of the users. The users are all in Nordic countries (Norway, Sweeden, Finland, etc.). This could be a coincidence, or just that the Nordic are more vocal when there is a bug, but this bug makes me nervous.
I've tried setting my phone to use Nynorsk as the default language and using Nordic international units (which my app does happily support with NSDateFormatter and friends). The app works fine for me when I do this, but I am physically in the USA. I've even gone so far as to use a user account with my app whose server-side data was generated in Finland (all sorts of diacritics in strings that will be saved with CoreData), and it still works as expected.
I don't see anything abnormal with my instantiation of my NSFetchedResultsController, but for completeness, here is one that returns no results:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil)
return (_fetchedResultsController);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:kTTAEntityCheckinDetail inManagedObjectContext:MOC];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:kTTACheckinDetailBatchSize];
NSSortDescriptor *checkinIDSort = [NSSortDescriptor sortDescriptorWithKey:kTTACheckinDetailAttributeCheckinID ascending:NO];
[fetchRequest setSortDescriptors:#[checkinIDSort]];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:MOC sectionNameKeyPath:#"sectionIdentifier" cacheName:nil];
_fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if ([self.fetchedResultsController performFetch:&error] == NO)
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
return (_fetchedResultsController);
}
It's possible that performFetch is failing, but I do not have access to the user's logs. Perhaps I should detect the user's location and log this to a remote server?
Solution: CoreData failed to save the objects because a date field was nil. This field wasn't populated because the locale of a NSDateFormatter was not set. See the comments of #flexaddicted's accepted solution for details.
This could be a shot in the dark.
First, what type of objects do you retrieve? Based on dates?
Then, Yes, I would log both locally and server-side. To log locally you could ask users in Nordic Countries to send a feedback (based on email) with Core Data store or just file logs (the logs will be inserted in the right points). The latter could be achieved by CocoaLumberjack.
An example of this can be found at CocoaLumberjack and Mailing logs by NSScreencast.
TestFlight could be an alternative way but I guess you are in production.

Resources