I have been trying to get this to work since it was announced a few years ago but with no joy. I'm struggling to get Apple's example code to behave itself too. Seems overly complex and buggy. So I set out to create a simplified version myself. I have got the database to sync with CloudKit and I can see my records in the developer dashboard. I'm trying to use container.record(for: object.objectID) to get the CKRecord for it, but this always fails. The next step would be to add the participant.
I try to add the participant based on this code:
Button
{
let record = fetchRecord(for: items[0]) //hack just to use the first record for dev testing
let share = CKShare(rootRecord: record)
let persistenceController = PersistenceController.shared
persistenceController.addParticipant(
emailAddress: "andrew@ambrit.com",
permission: .readWrite,
share: share)
{ share, error in
if let error = error
{
print("Error: \(error.localizedDescription)")
}
else if let share = share
{
print("Share updated successfully: \(share)")
}
}
}
label:
{
Label("Participants", systemImage: "person")
}
and
extension PersistenceController
{
func addParticipant(emailAddress: String, permission: CKShare.ParticipantPermission = .readWrite, share: CKShare,
completionHandler: ((_ share: CKShare?, _ error: Error?) -> Void)?)
{
let container = PersistenceController.shared.container
let lookupInfo = CKUserIdentity.LookupInfo(emailAddress: emailAddress)
let persistentStore = privatePersistentStore //share.persistentStore!
container.fetchParticipants(matching: [lookupInfo], into: persistentStore) { (results, error) in
guard let participants = results, let participant = participants.first, error == nil else
{
completionHandler?(share, error)
return
}
participant.permission = permission
participant.role = .privateUser
share.addParticipant(participant)
container.persistUpdatedShare(share, in: persistentStore)
{ (share, error) in
if let error = error
{
print("\(#function): Failed to persist updated share: \(error)")
}
completionHandler?(share, error)
}
}
}
}
My immediate problem is that when I call fetchRecord it doesn't find anything despite the record being available in the CloudKit dashboard.
func fetchRecord(for object: NSManagedObject) -> CKRecord
{
let container = PersistenceController.shared.container
print ("Fetching record \(object.objectID)")
if let record = container.record(for: object.objectID)
{
print("CKRecord ID: \(record.recordID)")
print("Record Name: \(record.recordID.recordName)")
return record
}
else
{
fatalError("Record not found")
}
}
I'm struggling to get Apple's example code to behave itself too. Seems overly complex and buggy.
Would you mind to share what difficulty you have when trying the Apple sample code? When tackling an issue related to Core Data + CloudKit, I typically start with the Sharing Core Data objects between iCloud users sample. It is indeed a bit complicated, but is also a minimal project that demonstrates the techniques needed for implementing Core Data + CloudKit sharing.
Using the sample has the following benefits:
-
The Readme of the sample details the steps of how to configure and run the sample, with which you can run and play the sample without looking into the code.
-
If you see the sample has any problem, you can communicate with us without providing the details of your own implementation, and the communication will be quick because we know the sample.
-
You can use the sample as a test bed to try your code.
My immediate problem is that when I call fetchRecord it doesn't find anything despite the record being available in the CloudKit dashboard.
I'd use the sample as a test bed to check if your fetchRecord
works. To do so, I found the Rate
menu item in PhotoContextMenu.swift
, and hooked your code to the action of the button, as shown below:
Button(action: {
//activeSheet = .ratingView(photo)
let container = PersistenceController.shared.persistentContainer
print ("Fetching record \(photo.objectID)")
if let record = container.record(for: photo.objectID) {
print("CKRecord ID: \(record.recordID)")
print("Record Name: \(record.recordID.recordName)")
}
}) {
MenuButtonLabel(title: "Rate", systemImage: "star")
}
I then did the following steps:
- Run the sample app on two devices (A and B) that are logged in with a samle iCloud account.
- Add a photo on device A, and observe the photo is synchronized to device B.
- On device A, right click the photo, and then click
Rate
menu.
At step 3, I observed the following output in my Xcode Console, meaning that record(for:) did find and return the CloudKit record:
Fetching record 0xb458b35aed5fa451 <x-coredata://FCBF4CF5-ADE4-446D-91E9-81B46C408D5E/Photo/p55> CKRecord ID: <CKRecordID: 0x600002b4b7c0; recordName=..., zoneID=com.apple.coredata.cloudkit.share....:defaultOwner> Record Name: ...
You can give it a try and see if you get the same result.
Note that step 2 is to be sure that the photo is synchronized to CloudKit. In the case where the photo isn't synchronized and hence the underlying CloudKit record doesn't exist yet, record(for:)
returns nil, and I believe that was what happened in your case.
Best,
——
Ziqiao Chen
Worldwide Developer Relations.