For every record that I save to CloudKit, I pass in a CKRecord.ID that is made using my own UUID like so:
let recordId = CKRecord.ID(recordName: myOwnUUID, zoneID: .default)
let record = CKRecord(recordType: recordType, recordID: recordId)
record.setValue(title, forKey: "title")
...
That UUID is part of my data model and does not change for each record.
I am able to delete the record successfully, again passing in the same CKRecord.ID:
func delete(_ item: MyItem, completion: @escaping (Result<Int, Error>) -> ()) {
let myOwnUUID = item.id // this is the exact same UUID that I used to create/save the record the first time.
let recordId = CKRecord.ID(recordName: myOwnUUID, zoneID: .default)
ckDatabase.delete(withRecordID: recordId) { (recordId, error) in
DispatchQueue.main.async {
if let error = error {
completion(.failure(error))
return
}
guard recordId != nil else {
completion(.failure(CloudKitHelperError.castFailure))
return
}
completion(.success(0))
}
}
}
So, delete works without any problems.
But when I try to modify the record I get the error: "Server Record Changed"
... "record to insert already exists"
:
<CKError 0x600000b82af0: "Partial Failure" (2/1011); "Failed to modify some records"; uuid = 9A077F5D-AB44-412C-9B87-BFBAE3C7B19B; container ID = "iCloud.com.xxx.yyy"; partial errors: { CC0A0279-F511-4EF4-BF89-4A3072EA1AEF:(_defaultZone:defaultOwner) = <CKError 0x600000b80240: "Server Record Changed" (14/2004); server message = "record to insert already exists"; uuid = 9A077F5D-AB44-412C-9B87-BFBAE3C7B19B> }>
Here is my modify code:
func modify(_ item: MyItem, completion: @escaping (Result<Int, Error>) -> ()) {
let myOwnUUID = item.id // this is the exact same UUID that I used to create/save the record the first time.
let recordId = CKRecord.ID(recordName: myOwnUUID, zoneID: .default)
let record = CKRecord(recordType: recordType, recordID: recordId)
record.setObject(item.title as __CKRecordObjCValue, forKey: "title")
let configuration = CKOperation.Configuration()
configuration.timeoutIntervalForRequest = 10
configuration.timeoutIntervalForResource = 10
let operation = CKModifyRecordsOperation(recordsToSave: [record], recordIDsToDelete: nil)
operation.configuration = configuration
operation.modifyRecordsCompletionBlock = { (savedRecords, _, error) in
DispatchQueue.main.async {
if savedRecords != nil, error == nil {
completion(.success(0))
}
if let error = error {
completion(.failure(error))
}
}
}
ckDatabase.add(operation)
}
By the way, ckDatabase
is set to: ckDatabase = CKContainer(identifier: "iCloud.com.xxx.yyy").privateCloudDatabase
.
So why am I not able to modify the existing record using the same CKRecord.ID that I used to create the record? The delete operation clearly recognizes it.
In general, you should always fetch CKRecord
object from back end instead of constructing it in memory. Only that way, cloudkit will initialize it properly.
Where is the code which gives you this record in the first place, instead of the following code?
let record = CKRecord(recordType: recordType, recordID: recordId)
Optionally, check the record save policy.
For details, see this answer.