Yes. It's right there in your posting title: the CloudKit public database. You can learn from any tutorials for CloudKit itself, skipping details relating to the private and shared databases.
Post
Replies
Boosts
Views
Activity
You can retrieve the user's Contact photo from the Contacts kit.
Did you ever resolve your issue? I too want to downsize an organization account to an individual one...
The table's dataSource is responsible for the table's data, so you could call the same code whether or not the CKRecord is nil, and when it's nil purge any existing data you had for the table, then refresh the table.
Alternatively, that same code could hide/show the table and show/hide an alternate view (with a label that says "No records", for instance) when you have no records.
Development and Production use entirely different containers, so records added to one don't exist in the other. So it's not like Development vs. Production is a simple attribute—it represents deep differences in configuration.
When you say "it doesn't work", can you clarify what exactly you're doing, what you're expecting to see, and what you are seeing instead?
When Alice and Bob are sharing a record and Alice changes something in that share, Bob will only see the change automatically if there's a subscription set up for it. If you are expecting a notification about the change, and you're seeing it work in Development but not in Production, verify that you have in fact established a subscription in Production.
If, on the other hand, you are attempting to fetch a shared record with what should be a reliable recordID and this works in Development but not in Production, then it might be something else.
Perhaps you didn't set up subscriptions in Production? You might have set them up in Development, then neglected to do in Production as well.
Supply a perRecordSaveBlock and perRecordDeleteBlock within that operation and maintain the cumulative results there, leaving the modifyRecordsResultBlock free to describe the operation as a whole.
I've determined that the icons/avatars are indeed available, but through ContactsKit rather than CloudKit. The images shown are all images you have personally associated with the user's Contact entry in your Contacts app.
How to retrieve the images:
let share = mySharedRecord.share
let participants = share.participants
let userIdentities = participants.map {$0.userIdentity}
let emails = userIdentities.compactMap {$0.lookupInfo?.emailAddress}
let store = CNContactStore()
let keysToFetch = [CNContactImageDataAvailableKey, CNContactImageDataKey, CNContactThumbnailImageDataKey] as [CNKeyDescriptor]
emails.forEach { email in
do {
let predicate = CNContact.predicateForContacts(matchingEmailAddress: email)
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keysToFetch)
let imageDatas: [Data] = contacts.compactMap {$0.imageData}
let images = imageDatas.map {UIImage(data: $0)}
return images
} catch {
// Handle the error
return nil
}
}
The following is an opinion. Informed, but still an opinion.
1a. If your app's core functionality requires iCloud and the user is not signed into iCloud, display a clear message telling the user that they'll need to sign in to iCloud to use your app and include a button to take them to the system page for signing in. Do not simply display an alert, but rather show it in an attractive, polite, and modeless/full-window presentation.
1b. If your app includes a non-essential feature that requires iCloud and the user is not signed into iCloud, display a modeless notification/affordance to indicate that the feature is inactive and will remain so until they sign into iCloud. The notification might take the form of a special icon positioned where that feature is normally accessed. Tapping that icon (or hovering, on a Mac) might pop up a brief explainer. If the feature's importance warrants it, a more prominent notification might be used. Modeless is better than modal.
Sync should be automatic. Users will expect this if they use your app across multiple devices. Making it automatic may also clarify what you choose to sync. User data should certainly sync, but user settings may not. If I set my background color to blue to match my blue iPhone, I might not appreciate that my silver iPad's background color is now blue as well. Resist the temptation to ask the user whether they'd like to sync this or that. Rather, make that determination yourself after carefully considering the value to the user.
Also, if possible give the user a way to initiate a manual sync—it's reassuring—and display an indicator like a green checkmark to indicate that the sync completed successfully or failed. One common way to provide a manual sync is by including a pull-to-refresh on the scrollview containing the user's content.
This turned out to be my fault. While it's true that I was refreshing the parent record before saving the child record, I was doing so in parallel with a prior save of another child record of the same parent, and the earlier save was completing before the later refresh.
Swift:
recordA.save() // recordA.parent == parent 1
recordB.save() // recordB.parent == parent 1
Here's what happens in those two calls. lines 1 and 4 were executed synchronously, then respectively initiating the async calls on lines 2 and 5, which in turn initiated the async calls on lines 3 and 6.
The problem was that line 5 was completing before line 3, but line 3 was completing before line 6, so line 6 was failing because parent 1 had changed in the meantime (by line 3).
1: Save record A with parent 1
2: refresh parent 1
3: save record A
4: Save record B with parent 1
5: refresh parent 1
6: save record B
I was naive to initiate lines 1 and 3 together and fortunate that they failed quickly, since they might have worked well—until they didn't.
There are two solutions I can think of here:
Batch records A and B together in a single operation.
Chain A > B so that B is initiated only after A completes.
The former solution is by far the preferable one, but the latter solution made more sense in my case, so the revised approach (which now works fine) looks like this:
In Swift, it looks much friendlier:
recordA.save() {
recordB.save() {
}
1: Save record A with parent 1
2: refresh parent 1
3: save record A
4: Save record B with parent 1
5: refresh parent 1
6: save record B
I still don't know what "chain PCS data" means, but in essence it seems to indicate that the records you're attempting to modify have changed and thus cannot be modified. The solution is to get a fresh copy of those records and try again. In my case, it meant get a fresh copy of the parent before saving record B.
When you configure your app to use CloudKit, 3 databases are created for you automatically: public, private, and shared. You don't create them yourself.
You'll need to read the documentation for a full understanding, but a good place to start is https://developer.apple.com/documentation/cloudkit/designing_and_creating_a_cloudkit_database.
You'll find it in CKContainer.containerIdentifier.
Thank you. Are you suggesting that the parent reference for childA is reused for childB, and the mismatch between the pre-Operation2 parent and the post-Operation1 parent reference is what's causing the error? In other words, that the creation of a parent reference updates/changes the referenced parent, perhaps to update its modification date?
I should add that I'm trying to understand how this works independently of the sharing controller. I appreciate that the sharing controller offers a "Stop Sharing" button for the owner and a "Remove Me" button for the joined participant. I was wondering about the programmatic approach.