I can't save a record, which is already saved. recordToSave is a CKRecord download from the server. Is it possible to update it?
recordTosave.setValue("cat", forKey: "animal")
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(recordToSave, completionHandler:{(record: CKRecord?, error: NSError?) in
if error == nil{
}else{
print(error.debugDescription)
}
})
You cannot insert record which is already existing in cloudkit. You can modify the record using CKModifyRecordsOperation. Fetch the record using record ID and then update through modify operation.
let recordIDToSave = CKRecordID(recordName: "recordID")
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.fetchRecordWithID(recordIDToSave) { (record, error) in
if let recordToSave = record {
//Modify the record value here
recordToSave.setObject("value", forKey: "key")
let modifyRecords = CKModifyRecordsOperation(recordsToSave:[recordToSave], recordIDsToDelete: nil)
modifyRecords.savePolicy = CKRecordSavePolicy.AllKeys
modifyRecords.qualityOfService = NSQualityOfService.UserInitiated
modifyRecords.modifyRecordsCompletionBlock = { savedRecords, deletedRecordIDs, error in
if error == nil {
print("Modified")
}else {
print(error)
}
}
publicData.addOperation(modifyRecords)
}else{
print(error.debugDescription)
}
}
Related
Good evening,
I have run into the issue of getting the following error message from Cloudkit when trying to save a record to the public database:
"Server Rejected Request" (15/2027); server message = "Custom zones are not allowed in public DB";
I have been able to find the issue causing this error. Within this application I need to fetch zone changes on the private database so I have had to save my records to a custom zone in order to accomplish that fetch.
The following code is where I am saving to the custom zone on the private database:
let zoneID = CKManager.defaultManager.sharedZone?.zoneID
let deckSetToBeSaved = deckName.deckToCKRecord(zoneID)
privateDatabase.save(deckSetToBeSaved) { (record, error) in
DispatchQueue.main.async {
if let record = record {
deckName.cKRecordToDeck(record)
try? self.managedContext.save()
}
}
print("New record saved")
print(error as Any)
}
}
Here is my code for saving a record to the public database:
func shareDeckPlan(_ deck: Deck, completion: #escaping (Bool, NSError?) -> Void ) {
var records = [CKRecord]()
let deckRecord = deck.deckToCKRecord()
records.append(deckRecord)
let reference = CKReference(record: deckRecord, action: .deleteSelf)
for index in deck.cards {
let cardRecord = index.cardToCKRecord()
cardRecord["deck"] = reference
records.append(cardRecord)
}
let operation = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: nil)
operation.modifyRecordsCompletionBlock = {(savedRecords, deletedIDs, error) in
if error == nil {
DispatchQueue.main.async(execute: {
if let savedRecords = savedRecords {
for record in savedRecords {
if record.recordID.recordName == deck.ckRecordID {
deck.cKRecordToDeck(record)
}
for cards in deck.cards {
if record.recordID.recordName == cards.ckRecordID {
cards.ckRecordToCard(record)
}
}
}
}
let _ = try? self.managedContext.save()
DispatchQueue.main.async(execute: {
completion(true, error as NSError?)
})
})
} else {
DispatchQueue.main.async(execute: {
print(error!)
completion(false, error as NSError?)
})
}
}
operation.qualityOfService = .userInitiated
self.publicDatabase.add(operation)
}
The following code referes to the deckToCKRecord method:
func deckToCKRecord(_ zoneID:CKRecordZoneID? = nil) -> CKRecord {
let deckSetName: CKRecord
if let ckMetaData = ckMetaData {
let unarchiver = NSKeyedUnarchiver(forReadingWith: ckMetaData as Data)
unarchiver.requiresSecureCoding = true
deckSetName = CKRecord(coder: unarchiver)!
}
else {
if let zoneID = zoneID {
deckSetName = CKRecord(recordType: "Deck", zoneID: zoneID)
self.ckRecordID = deckSetName.recordID.recordName
} else {
deckSetName = CKRecord(recordType: "Deck")
self.ckRecordID = deckSetName.recordID.recordName
}
}
deckSetName["name"] = name! as CKRecordValue
deckSetName["cardCount"] = cards.count as CKRecordValue
return deckSetName
}
How can I save a record both to the private database in a custom zone and the public database successfully?
I am saving a record to a public database in an app that is currently under external testing.
One of my testers has a problem that I can seem to work out - she saves the record, which the program thinks saves currently, and throws no errors, but the record doesn't show up on iCloud, and we cannot retrieve the record.
Other testers have not had this issue - only her. Her iCloud and iCloud drive is turned on and enabled for the app.
The code too save the record:
let newRecord = CKRecord(recordType: "Club")
loadedRecord = newRecord
publicDB.save(newRecord) { (nil, error) in
if error != nil {
dispError = String(describing: error)
print("Create Club Error = \(String(describing: error))")
} else {
//dispError = nil
clubCreated[0] = true
}
}
The code to retrieve the record:
let namePredicate = NSPredicate(format: "self contains '\(predSearchTerm)'")
print(predSearchTerm)
let query = CKQuery(recordType: ClubType, predicate: namePredicate)
publicDB.perform(query, inZoneWith: nil) { [unowned self] results, error in
if error != nil {
DispatchQueue.main.async {
dispError = String(describing: error)
print("Cloud Query Error - Fetch Clubs: \(dispError)")
}
return
}
//dispError = nil
self.items.removeAll(keepingCapacity: true)
results?.forEach({ (record: CKRecord) in
loadedRecord = results?[0]
self.items.append(Club(record: record, database: self.publicDB))
})
clubLoaded = true
}
When I check to see the number of items retrieved it says 0, and the record cannot be found on iCloud.
I am trying to save a CKRecordID to NSUserDefaults. How can I get it back in its original type as a CKRecordID and not NSData when it is retrieved? Can you use NSKeyArchiver in this way or is there an alternate method?
func getRecordToUpdate(_ locations:CLLocation)
{
if defaults1.object(forKey: "locationData") == nil{
locationRecord.setObject(locations, forKey: "location")
let id = locationRecord.recordID
let dataId = NSKeyedArchiver.archivedData(withRootObject: id)
defaults1.set(dataId, forKey: "locationData")
self.updateLocationRecord(locations: locations)
}else{
if let loadedData = defaults1.object(forKey: "locationData") {
if (NSKeyedUnarchiver.unarchiveObject(with: loadedData as! Data)) != nil
{
let publicDB = CKContainer.default().publicCloudDatabase
let lit = NSKeyedUnarchiver.unarchiveObjectWithData(loadedData as! CKRecordID)
publicDB.fetch(withRecordID: lit ,completionHandler: {
(record, error) in
if error == nil
{
publicDB.delete(withRecordID: (record?.recordID)!, completionHandler: {
(record, error) in
if(error == nil){
print("old record delete")
let id = self.locationRecord.recordID.recordName
self.locationRecord.setObject(locations, forKey: "location")
self.defaults1.set(id, forKey: "locationData")
self.updateLocationRecord(locations: locations)
}
else{
}
})
}else{
print("Error fetching previous record")
}
})
}
}
}
}
After you saved the recordID the first time, it's guaranteed to be Data representing a CKRecordID object.
Read the raw data from user defaults and unarchive the data to the record id.
if let loadedData = defaults1.object(forKey: "locationData") as? Data {
let lit = NSKeyedUnarchiver.unarchiveObjectWithData(loadedData) as! CKRecordID
let publicDB = CKContainer.default().publicCloudDatabase
publicDB.fetch ...
I am using the code below to update a record. Once the record has been updated I would like to run my refresh function. At the moment the refresh function is sometimes called before the record has been updated so the refresh results are the same as before the record was updated.
Thanks
var tempDocumentsArray:NSArray!
let recordID = CKRecordID(recordName: "layerAbove")
var predicate = NSPredicate(format: "recordID = %#", recordID)
let query = CKQuery(recordType: "Layers", predicate: predicate)
self.publicDB.performQuery(query, inZoneWithID: nil) { (results, error) -> Void in
tempDocumentsArray = results
print("Results are: \(tempDocumentsArray)")
let record = tempDocumentsArray[0] as! CKRecord
var layerAbovePrevPos = record.objectForKey("layerNumber") as! Int
layerAbovePrevPos = layerAbovePrevPos - 1
let nlnChanged = record.setObject(layerAbovePrevPos, forKey: "layerNumber")
self.publicDB.saveRecord(record, completionHandler: { (returnRecord, error) -> Void in
if let err = error {
print("Error: \(err.localizedDescription)")
} else {
dispatch_async(dispatch_get_main_queue()) {
print("Success")
//TODO:This is sometimes called before the save is complete!
self.resetAndGet()
}
}
})
}
How do I delete a CKRecord from my database programmatically, on the client side.
for record in records {
if recordNumber < 13 {
let moodRecord:CKRecord = record as! CKRecord
self.moodArray.append(moodRecord.objectForKey("Color") as! String)
}
else if recordNumber > 13 {
// DELETE RECORD HERE
record.delete(CKRecord)
recordNumber--
}
recordNumber++
}
That's not how you delete records in CloudKit. Here's how I delete records in my app:
let operation = CKModifyRecordsOperation(recordsToSave: nil, recordIDsToDelete: [record.recordID])
operation.savePolicy = .AllKeys
operation.modifyRecordsCompletionBlock = { added, deleted, error in
if error != nil {
println(error) // print error if any
} else {
// no errors, all set!
}
}
CKContainer.defaultContainer().publicCloudDatabase.addOperation(operation)
You can use
delete(withRecordID:completionHandler:)
apple docs
objc version:
[aDatabase deleteRecordWithID:rec.recordID completionHandler:^(CKRecordID * _Nullable recordID, NSError * _Nullable error) {
;
}];
let database = CKContainer.default().publicCloudDatabase // or privateCloudDatabase
let recordID = someRecord.recordID
database.delete(withRecordID: recordID) { (ckRecordID, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let id = ckRecordID else {
return
}
print(id)
}