One of our goals here at Tact is to share bits and pieces of our work that may be of use to others. This is the first such post.

Tact talks to CloudKit in various forms, and one useful form is CloudKit subscriptions like CKQuerySubscription. It’s straightforward to create a subscription against a predicate. Whenever a record with that predicate is created, modified or deleted in the specified database, you get a push notification.

I needed to create a subscription with the predicate “items that I have created”. It’s not obvious how to do that. I couldn’t find an easily googleable recipe.

To create a query subscription (or just a query, for that matter), we need a predicate. The basic form of that predicate is…

let predicate = NSPredicate(format: "creatorUserRecordID = %@", .. what to put here ..?)

Knowing that all records created by myself have CKCurrentUserDefaultName as the creator ID, we could try this:

let predicate = NSPredicate(format: "creatorUserRecordID = %@", CKRecord.ID(recordName: CKCurrentUserDefaultName))

That doesn’t work, though.

When you inspect your CloudKit schema in the CloudKit web backend, you see a bunch of system fields with the “reference” type, including createdBy. (Don’t ask me why it’s createdBy in the dashboard UI, but creatorUserRecordID in code. That’s one of the many esoteric mysteries you’ll find working with CloudKit.)

Here’s what the system fields for this record look like.

CloudKit system fields

So we get to the final correct working version of the predicate.

let predicate = NSPredicate(format: "creatorUserRecordID = %@", CKRecord.Reference(recordID: CKRecord.ID(recordName: CKCurrentUserDefaultName), action: .none))

And here’s a full working method of creating a CloudKit query subscription with this predicate.

let predicate = NSPredicate(format: "creatorUserRecordID = %@", CKRecord.Reference(recordID: CKRecord.ID(recordName: CKCurrentUserDefaultName), action: .none))

let notificationInfo = CKSubscription.NotificationInfo()
notificationInfo.shouldSendContentAvailable = true
        
let subscription = CKQuerySubscription(
    recordType: "MyRecordType",
    predicate: predicate,
    subscriptionID: "SomeSubscription-v1",
    options: [.firesOnRecordUpdate])
subscription.notificationInfo = notificationInfo
        
let container = CKContainer(identifier: CloudKitContainerIdentifier)
        
container.publicCloudDatabase.save(subscription) { subscription, error in
    // handle the result
}

This StackOverflow question was helpful in pointing me in the right direction.

One bit to note is that for the above predicate and subscription to work, you need to add “Queryable” index to “createdBy” system field, as you see on the above screenshot. Otherwise, CloudKit gives you an error about this field not being queryable. (I originally did this in development schema, but forgot to migrate it to production, and production gave me strange errors until I figured this out.)