CloudKit saveRecord Complete - ios

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()
}
}
})
}

Related

Can't fetch userCKRecordID with Swift

I am getting the error: "Value of type 'CKDatabase' has no member 'fetchUserRecordIDWithCompletionHandler'." Was it taken out of the newest version of Xcode or swift 3?
func fetchUserRecords()
{
let publicDB = CKContainer.default().publicCloudDatabase
publicDB.fetchUserRecordIDWithCompletionHandler { (userID, error) -> Void in
if let userID = userID {
let reference = CKReference(recordID: userID, action: .None)
let predicate = NSPredicate(format: "creatorUserRecordID == %#", reference)
let query = CKQuery(recordType: "Location", predicate: predicate)
CKContainer.default().publicCloudDatabase.perform(query, inZoneWith: nil){
(records, error) in
if error != nil {
print("error fetching user records: \(error)")
completion(error as NSError?, nil)
} else {
print("found user records")
completion(nil, records)
guard let records = records else {
return
}
for record in records
{
//delete records
}
}
}
}
}
}
Swift 3 changes the names of lots of function calls. The new function signature is
func fetchUserRecordID(completionHandler: #escaping (CKRecordID?, Error?) -> Void)
EDIT:
rmaddy reports that the function is a function of CKContainer, not CKDatabase.

How to download multiple records from cloudkit

I am trying to download multiple items from cloud kit but I'm getting the error "cannot assign type value (CKQueryCursor!, NSError) -> () to type (CKQueryCursor?, NSError?) -> void"
let locationToLookFor = CLLocation()
let predicate = NSPredicate(format: "location = %#", locationToLookFor as CLLocation)
let query = CKQuery(recordType: "Location", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.recordFetchedBlock = self.recordFetchBlock
operation.queryCompletionBlock =
{
[weak self]
(cursor: CKQueryCursor!, error: NSError) in
if(cursor != nil)
{
print("Fetching records")
let newOperation = CKQueryOperation(cursor: cursor)
operation.recordFetchedBlock = recordFetchBlock
operation.queryCompletionBlock = operation.queryCompletionBlock
self!.operationQueue.addOperation(newOperation)
}
else {
print("We have fetched all data")
}
}
operationQueue.addOperation(operation)
Your closure signature doesn't match the required signature. As shown in the error message, cursor should be optional as should error. You will also get an error because you don't unwrap cursor when you supply it to the new operation.
Try:
operation.queryCompletionBlock =
{
[weak self]
(cursor: CKQueryCursor?, error: NSError?) -> Void in
if let cursor = cursor
{
print("Fetching records")
let newOperation = CKQueryOperation(cursor: cursor)
operation.recordFetchedBlock = recordFetchBlock
operation.queryCompletionBlock = operation.queryCompletionBlock
self?.operationQueue.addOperation(newOperation)
}
else {
print("We have fetched all data")
}
}

CloudKit private database returns first 100 CKRecords

i am facing this kind of problem working with CloudKit. Trying to fetch all data from "Data" record. Result is limited by 100. How to get all data? Please, thank for any advice.
func getAllDataFromCloudKit(){
let predicate = NSPredicate(value: true)
let container = CKContainer.defaultContainer()
let privateDatabase = container.privateCloudDatabase
let query = CKQuery(recordType: "Data", predicate: predicate)
privateDatabase.performQuery(query, inZoneWithID: nil) { results, error in
if error != nil {
print(error)
}
else {
for result in results! {
// return only 100 first
}
}
}
}
P.S. i found one similar question, still not clear or answer is too old and does not work with the new Swift version
EDIT: See my final solution how to get all data from private database below:
Ok, i found a solution. See below:
func loadDataFromCloudKit() {
var results: [AnyObject] = []
let cloudContainer = CKContainer.defaultContainer()
let privateDatabase = cloudContainer.privateCloudDatabase
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Data", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
let queryOperation = CKQueryOperation(query: query)
queryOperation.desiredKeys = ["id","name"]
queryOperation.queuePriority = .VeryHigh
// Max limit is still 100
queryOperation.resultsLimit = 100
queryOperation.recordFetchedBlock = { (record:CKRecord!) -> Void in
results.append(record)
}
queryOperation.queryCompletionBlock = { (cursor, error) in
dispatch_async(dispatch_get_main_queue()) {
if (error != nil) {
print("Failed to get data from iCloud - \(error!.localizedDescription)")
} else {
print("Successfully retrieve the data form iCloud")
}
}
// see cursor here, if it is nil than you have no more records
// if it has a value than you have more records to get
if cursor != nil {
print("there is more data to fetch")
let newOperation = CKQueryOperation(cursor: cursor!)
newOperation.recordFetchedBlock = { (record:CKRecord!) -> Void in
results.append(record)
}
newOperation.queryCompletionBlock = queryOperation.queryCompletionBlock
privateDatabase.addOperation(newOperation)
} else {
// gets more then 100
print(results.count)
}
}
privateDatabase.addOperation(queryOperation)
}
Kevin,
You use the cursor returned by CKQueryOperation; it is very much a standard approach in the world of databases; I know some dropbox operations use the same approach for example. Here is the basic code for a CKQueryOperation.
func query4Cloud(theLink: String, theCount: Int) {
var starCount:Int = 0
let container = CKContainer(identifier: "iCloud.ch")
let publicDB = container.publicCloudDatabase
let singleLink2LinkthemALL = CKRecordID(recordName: theLink)
let recordToMatch = CKReference(recordID: singleLink2LinkthemALL, action: .DeleteSelf)
let predicate = NSPredicate(format:"theLink == %#", recordToMatch)
let query = CKQuery(recordType: "Files", predicate: predicate)
// You run the query operation replacing query with your cursor
readerOperation = CKQueryOperation(query: query)
readerOperation.desiredKeys = ["record.recordID.recordName"];
readerOperation.recordFetchedBlock = { (record) in
starCount += 1
}
// see cursor here, if it is nil than you have no more records
// if it has a value than you have more records to get
readerOperation.queryCompletionBlock = {(cursor, error) in
print("fcuk query4Cloud \(theLink) \(theCount) \(starCount)" )
if error != nil {
self.showAlert(message: error!.localizedDescription)
print("ting, busted")
} else {
// it's done
}
}
print("publicDB.addOperation(operation)")
readerOperation.qualityOfService = .Background
publicDB.addOperation(readerOperation)
}

How do I query and obtain data from CloudKit?

I am fairly new to Swift, and just started learning about CloudKit this week for an iOS app project.
The database is working, I can add records and find records in the database.
I have run into a problem sending a query to CloudKit and accessing the data related to the query.
The CloudKit data has unique identifiers, so the search is for one of those identifiers (so a query should only return one record). I am then trying to obtain three pieces of information from that record - "UPC", "foodName", and "Ingredients" (all strings)
Here is the code section that executes the query and tries to obtain the data.
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
let predicate = NSPredicate(format: "UPC = %#", subStr)
let query = CKQuery(recordType: "Food", predicate: predicate)
publicDatabase.performQuery(query, inZoneWithID: nil,
completionHandler: ({results, error in
if (error != nil) {
dispatch_async(dispatch_get_main_queue()) {
print("CloudKit Error")
}
} else {
if results!.count > 0 {
var record = results as! CKRecord
dispatch_async(dispatch_get_main_queue()) {
print("UPC Found")
let cloudUPC = record.objectForKey("UPC") as! CKAsset
print("UPC from CloudKit \(cloudUPC)")
}
} else {
dispatch_async(dispatch_get_main_queue()) {
print("UPC Not Found")
}
}
}
}))
The crash occurs at this point
var record = results as! CKRecord
and returns "EXC_Breakpoint(code = 1, subcode - 0x10047b7c)
Any suggestions on how to solve this problem?
Thanks
Thank you for the suggestion rmaddy.
This is how I fixed the code
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
let predicate = NSPredicate(format: "UPC = %#", subStr)
let query = CKQuery(recordType: "Food", predicate: predicate)
publicDatabase.performQuery(query, inZoneWithID: nil,
completionHandler: ({results, error in
if (error != nil) {
dispatch_async(dispatch_get_main_queue()) {
print("Cloud Error")
}
} else {
if results!.count > 0 {
dispatch_async(dispatch_get_main_queue()) {
for entry in results! {
let cloudUPC = entry["UPC"] as? String
print("UPC from CloudKit \(cloudUPC)")
let cloudFoodName = entry["foodName"] as? String
print("Name from CloudKit \(cloudFoodName)")
let cloudIngredients = entry["Ingredients"] as? String
print("Ingredients from CloudKit \(cloudIngredients)")
}
} else {
dispatch_async(dispatch_get_main_queue()) {
print("UPC Not Found")
}
}
}
}))
you need to first to write for loop on items in record then print each UPC
like that :
for item in record {
print(item["UPC"]
print(item["foodName"]
}

CKQueryOperation error: This operation has been rate limited

Client received error 1298: This operation has been rate limited error from CloudKit when downloading records with CKQueryOperation, only once, during Apple review. How can I fix this issue?
Here is to code, nothing special:
let query = CKQuery(recordType: "Movie", predicate: NSPredicate(format: "creationDate > %#", d!))
let qo = CKQueryOperation(query: query)
let fb: (CKRecord!) -> () = {record in
temporaryContext.performBlockAndWait({
let fr = NSFetchRequest(entityName: "Movie")
fr.predicate = NSPredicate(format: "recordName = %#", record.recordID.recordName)
let a = temporaryContext.executeFetchRequest(fr, error: nil) as! [Movie]
if a.count == 0 {
let m = NSEntityDescription.insertNewObjectForEntityForName("Movie", inManagedObjectContext: temporaryContext) as! Movie
m.title = record.valueForKey("title") as! String
m.image = (record.valueForKey("image") as! CKAsset).fileURL.description
m.imageSize = Int32(record.valueForKey("imageSize") as! Int)
m.recordName = record.recordID.recordName
}
})
}
let c: ()->() = {
temporaryContext.performBlockAndWait({
let success = temporaryContext.save(nil)
})
Utility.managedObjectContext().performBlockAndWait({
let success = Utility.managedObjectContext().save(nil)
})
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "moviesDownloaded")
NSUserDefaults.standardUserDefaults().synchronize()
dispatch_semaphore_signal(self.sema)
}
let cb: (CKQueryCursor!, NSError!) -> () = {cursor, error in
if error == nil {
if cursor != nil {
let qo2 = Utility.qo(cursor, recordFetchedBlock: fb, completion: c)
publicDatabase.addOperation(qo2)
} else {
c()
}
} else {
Utility.log("error 1298: \(error.localizedDescription)")
dispatch_sync(dispatch_get_main_queue(), {
self.status.backgroundColor = UIColor.orangeColor()
})
NSThread.sleepForTimeInterval(0.5)
dispatch_semaphore_signal(self.sema)
}
}
qo.recordFetchedBlock = fb
qo.queryCompletionBlock = cb
publicDatabase.addOperation(qo)
dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER)
I try to put this whole code into a loop like:
for i in 1 ... 2 {
var rateLimited = false
...
if error == nil {
} else {
NSThread.sleepForTimeInterval(3)
rateLimited = true
}
...
if !rateLimited {
break
}
}
Do you think it will work?
If you get CKErrorRequestRateLimited the error will have a CKErrorRetryAfterKey key in the error's userInfo dictionary. You should wait at least that amount of time before retrying your operation.
Waiting with a sleep is a bad idea because it can cause unexpected hangs in your application, especially if that code runs on your main thread. Use dispatch_after or a NSTimer to re-send your operation.
You will also get this error if you are not logged in to your iCloud account.

Resources