How do I query and obtain data from CloudKit? - ios

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"]
}

Related

How would I query CloudKit for a specific record by its recordName?

How would I query CloudKit for a specific record by its recordName?
The following code gives me an error.
let recordName = "BBBA9068-D01A-9D55-3D6B-4460A7B36D57"
let predicate = NSPredicate(format: "recordID=%#", recordName)
let query = CKQuery(recordType: "Route", predicate: predicate)
privateDatabase.perform(query, inZoneWith: nil) {
(records: [CKRecord]?, error: Error?) in
if error != nil {
print(error!.localizedDescription)
} else {
if let records = records {
print(records.first as Any)
}
}
}
The debug window shows:
Field '___recordID' has a value type of REFERENCE and cannot be queried using filter value type STRING
I changed the first two lines of the code and got it to work.
let recordID: CKRecord.ID = CKRecord.ID(recordName: "BBBA9068-D01A-9D55-3D6B-4460A7B36D57")
let predicate = NSPredicate(format: "recordID=%#", recordID)
let query = CKQuery(recordType: "Route", predicate: predicate)
privateDatabase.perform(query, inZoneWith: nil) {
(records: [CKRecord]?, error: Error?) in
if error != nil {
print(error!.localizedDescription)
} else {
if let records = records {
print(records.first as Any)
}
}
}
GenerateCKRecord.ID by your recordName
For one recordName you can do like that:
let recordID = CKRecord.ID(recordName: "recordName", zoneID: "Zone")
if you don't use zones, will automatically use the default zone
let recordID = CKRecord.ID(recordName: "recordName")
For multiple recordIDs:
let recordIDs = recordNames.map { CKRecord.ID(recordName: $0) }
If you want to use predicate:
let recordIDs = recordNames.map { CKRecord.ID(recordName: $0) }
let predicate: NSPredicate = NSPredicate(format: "recordID IN %#", recordIDs)
Hopefully my answer meet your needs

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.

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

CloudKit saveRecord Complete

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

Why did my app crash when using cloudkit?

When I try to load data from iCloud using Swift, the app crashes with this error. Why is that? I can't seem to find the bug?
Code:
func LoadEvents() {
let predicate:NSPredicate = NSPredicate(value: true)
let query:CKQuery = CKQuery(recordType: "Data", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
if let database = self.publicDatabase {
database.performQuery(query, inZoneWithID: nil, completionHandler: { (records:[AnyObject]!, error:NSError!) in
if error != nil {
self.alert("Error: \(error.localizedDescription)", Message: "Make sure iCloud is turned on and you are connected to the internet")
} else {
dispatch_async(dispatch_get_main_queue()) {
self.EventsArray.removeAll(keepCapacity: false)
for record in records {
let usernameRecord:CKRecord = record as CKRecord
self.EventsArray.insert(usernameRecord.objectForKey("Events") as String, atIndex: 0)
println("1")
}
//update data
self.collectionView.reloadData()
}
}
})
}
}
The record that you are reading probably does not have a field named Events. So the objectForKey will return nil which cannot be casted to string. You could solve this by using as? String or use:
if let event = usernameRecord.objectForKey("Events") as? String{
self.EventsArray.insert(event, atIndex: 0)
}

Resources