Ok, not sure this is a cut'n'paste answer, but will give you a template to work with. It shows extensive error code handling, with an action next to the retry with the save action for example. You should be able work out how to do what you want to do with this code.
func files_saveSet() {
let newRecord = CKRecord(recordType: "Blah", recordID: sharedDataAccess.iCloudID)
newRecord["Key"] = sharedDataAccess.iCloudLink as CKRecordValue?
var localChanges:[CKRecord] = []
let records2Erase:[CKRecordID] = []
localChanges.append(newRecord)
let saveRecordsOperation = CKModifyRecordsOperation(recordsToSave: localChanges, recordIDsToDelete: records2Erase)
saveRecordsOperation.savePolicy = .changedKeys
saveRecordsOperation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordIDs, error in
self.theApp.isNetworkActivityIndicatorVisible = false
guard error == nil else {
if let ckerror = error as? CKError {
if ckerror.code == CKError.requestRateLimited {
let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
DispatchQueue.main.async {
Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.files_saveSet), userInfo: nil, repeats: false)
}
} else if ckerror.code == CKError.zoneBusy {
let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
DispatchQueue.main.async {
Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.files_saveSet), userInfo: nil, repeats: false)
}
} else if ckerror.code == CKError.limitExceeded {
let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
DispatchQueue.main.async {
Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.files_saveSet), userInfo: nil, repeats: false)
}
} else if ckerror.code == CKError.notAuthenticated {
NotificationCenter.default.post(name: Notification.Name("noCloud"), object: nil, userInfo: nil)
} else if ckerror.code == CKError.networkFailure {
NotificationCenter.default.post(name: Notification.Name("networkFailure"), object: nil, userInfo: nil)
} else if ckerror.code == CKError.networkUnavailable {
NotificationCenter.default.post(name: Notification.Name("noWiFi"), object: nil, userInfo: nil)
} else if ckerror.code == CKError.quotaExceeded {
NotificationCenter.default.post(name: Notification.Name("quotaExceeded"), object: nil, userInfo: nil)
} else if ckerror.code == CKError.partialFailure {
NotificationCenter.default.post(name: Notification.Name("partialFailure"), object: nil, userInfo: nil)
} else if (ckerror.code == CKError.internalError || ckerror.code == CKError.serviceUnavailable) {
NotificationCenter.default.post(name: Notification.Name("serviceUnavailable"), object: nil, userInfo: nil)
}
} // end of guard statement
return
}
if error != nil {
//print(error!.localizedDescription)
} else {
//print("ok")
}
}
saveRecordsOperation.qualityOfService = .background
privateDB.add(saveRecordsOperation)
theApp.isNetworkActivityIndicatorVisible = true
}