Swift CloudKit SaveRecord "Error saving record"
cloudkit, ios, ios8, swift
Solution
A record can be saved to iCloud using CKDatabase's convenience method `saveRecord:` or via a CKModifyRecordsOperation. If it's a single record, you can use `saveRecord:` but will need to fetch the record you'd like to modify using `fetchRecordWithID:` prior to saving it back to iCloud. Otherwise, it will only let you save a record with a new RecordID. More here.
database.fetchRecordWithID(recordId, completionHandler: { record, error in
if let fetchError = error {
println("An error occurred in \(fetchError)")
} else {
// Modify the record
record.setObject(newName, forKey: "name")
}
}
database.saveRecord(aRecord, completionHandler: { record, error in
if let saveError = error {
println("An error occurred in \(saveError)")
} else {
// Saved record
}
}
The code above is only directionally correct but won't work as is because by the time the completionHandler of fetchRecordWithID returns, saveRecord will have fired already. A simple solution would be to nest saveRecord in the completionHandler of fetchRecordWithID. A probably better solution would be to wrap each call in a `NSBlockOperation` and add them to an NSOperationQueue with saveOperation dependent on fetchOperation.
This part of your code would be for a `CKModifyRecordsOperation` and not needed in case you are only updating a single record:
var ops:CKModifyRecordsOperation = CKModifyRecordsOperation()
ops.savePolicy = CKRecordSavePolicy.IfServerRecordUnchanged
database.addOperation(ops)
If you do use a `CKModifyRecordsOperation` instead, you'll also need to set at least one completion block and deal with errors when conflicts are detected with existing records:
let saveRecordsOperation = CKModifyRecordsOperation()
var ckRecordsArray = [CKRecord]()
// set values to ckRecordsArray
saveRecordsOperation.recordsToSave = ckRecordsArray
saveRecordsOperation.savePolicy = .IfServerRecordUnchanged
saveRecordsOperation.perRecordCompletionBlock { record, error in
// deal with conflicts
// set completionHandler of wrapper operation if it's the case
}
saveRecordsOperation.modifyRecordsCompletionBlock { savedRecords, deletedRecordIDs, error in
// deal with conflicts
// set completionHandler of wrapper operation if it's the case
}
database.addOperation(saveRecordsOperation)
There isn't much sample code yet besides the CloudKitAtlas demo app, which is in Objective-C. Hope this helps.
Problem
I am trying to save a record to CloudKit but am getting an error. I had seen elsewhere that this was an issue that required knowing how to save but I can't get this to work. ``` var database:CKDatabase = CKContainer.defaultContainer().publicCloudDatabase var aRecord:CKRecord! if self.cloudId == nil { var recordId:CKRecordID = CKRecordID(recordName: "RecordId") self.cloudId = recordId // Setup at top } aRecord = CKRecord(recordType: "RecordType", recordID: self.cloudId) aRecord.setObject(self.localId, forKey: "localId") // Set the normal names etc aRecord.setObject(self.name, forKey: "name") var ops:CKModifyRecordsOperation = CKModifyRecordsOperation() ops.savePolicy = CKRecordSavePolicy.IfServerRecordUnchanged database.addOperation(ops) database.saveRecord(aRecord, completionHandler: { (record, error) in if error != nil { println("There was an error \(error.description)!") } else { var theRecord:CKRecord = record as CKRecord self.cloudId = theRecord.recordID } }) ``` This gives me the error: ``` There was an error <CKError 0x16d963e0: "Server Record Changed" (14/2017); "Error saving record <CKRecordID: 0x15651730; xxxxxx:(_defaultZone:__defaultOwner__)> to server: (null)"; uuid = 369226C6-3FAF-418D-A346-49071D3DD70A; container ID = "iCloud.com.xxxxx.xxxx-2">! ``` Not sure, given that I have added CKModifyRecordsOperation. Sadly there is no examples within Apple's documentation. I miss that (which you get on MSDN). Thanks peeps!