I want to fetch only Song_Name field in Musics records in CloudKit. I'm trying below code but It still fetches all the fields in Music record. I think I need to compile operation with publicDB.add(operation) method but It doesn't allow me to declare ¨results¨ as I do now with publicDB.perform(query, in....)
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Musics", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["Song_Name"]
publicDB.perform(query, inZoneWith: nil) { [unowned self] results, error in
guard error == nil else {
DispatchQueue.main.async {
self.delegate?.errorUpdating(error! as NSError)
print("Cloud Query Error - Refresh: \(error)")
}
return
}
self.items_music.removeAll(keepingCapacity: true)
for record in results! {
let music = Musics(record: record, database: self.publicDB)
self.items_music.append(music)
}
DispatchQueue.main.async {
self.delegate?.modelUpdated()
}
}
}
You are creating a CKQueryOperation and setting the operation's desiredKeys property, but then you are not using this operation. You simply perform a query against the database, so none of the query operation's properties will be used.
You need to assign a record fetched block and query completion block to your operation and then add the operation to the database to actually run it.
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Musics", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["Song_Name"]
var newItems = [Musics]()
operation.queryCompletionBlock = ( { (cursor, error)->Void in
guard error == nil else {
DispatchQueue.main.async {
self.delegate?.errorUpdating(error! as NSError)
print("Cloud Query Error - Refresh: \(error)")
}
return
}
self.items_music = newItems
DispatchQueue.main.async {
self.delegate?.modelUpdated()
}
})
operation.recordFetchedBlock = ( { (record) -> Void in
let music = Musics(record: record, database: self.publicDB)
newItems.append(music)
})
publicDB.add(operation)
I have omitted some code that you will need that checks the cursor provided to the completion block. If this isn't nil then you need to issue another query to fetch additional items
Related
Hi i'm trying to fetch all records from my publicDB in cloudkit, and currently there are more of 2000 records. How can I fetch them all and put them in an array?
I've tryed these two methods without success. Can you please help me?
1 Approach
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Position", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
publicDB.perform(query, inZoneWith: nil) { (results, error) -> Void in
if error != nil {
DispatchQueue.main.async(execute: { () -> Void in
self.delegate?.errorUpdating(error: error! as NSError)
return
})
} else {
self.positionArray.removeAll(keepingCapacity: true)
for record in results! {
let position = Position(record: record as CKRecord, database: self.publicDB)
self.positionArray.append(position)
}
}
DispatchQueue.main.async(execute: { () -> Void in
/* my elaboration with the complete result */
})
}
2 Approach
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Position", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let qop = CKQueryOperation(query: query)
qop.resultsLimit = 3000
qop.recordFetchedBlock = { (record: CKRecord) in
let position = Position(record: record, database: self.publicDB)
self.positionArray.append(position)
print(self.positionArray.count)
}
qop.queryCompletionBlock = { (cursor: CKQueryOperation.Cursor?, error: Error?) in
DispatchQueue.main.async(execute: { () -> Void in
if let cursor = cursor {
print("entro")
let newOperation = CKQueryOperation(cursor: cursor)
newOperation.recordFetchedBlock = qop.recordFetchedBlock
newOperation.queryCompletionBlock = qop.queryCompletionBlock
self.publicDB.add(newOperation)
}
else if let error = error {
print("Error:", error)
}
// No error and no cursor means the operation was successful
else {
print("Finished with records:", self.positionArray)
if(!all){
// my elaboration
}
else{
// my elaboration
}
}
})
}
self.publicDB.add(qop)
With the first approach I can fetch at most 100 records.
With the second approach I can fetch at most 400 records.
But i need to fill my array with the all over 2000 records, how can achieve the result?
Plese, Try this one!
var Whistle = [CKRecord] ()
func loadWhistles(completionHandler: (() -> Void)?) {
let pred = NSPredicate(value: true)
let sort = NSSortDescriptor(key: nomeDaTabela, ascending: ordemDaTabela)
let query = CKQuery(recordType: "Cliente", predicate: pred)
query.sortDescriptors = [sort]
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["Name"]
operation.resultsLimit = 2000
var newWhistles = Whistle
operation.recordFetchedBlock = { record in
newWhistles.append(record)
}
operation.queryCompletionBlock = { [unowned self] (cursor, error) in
DispatchQueue.main.async {
if error == nil {
//ViewController.isDirty = false
self.clienteRecords = newWhistles
self.clientesTableView.reloadData()
} else {
let ac = UIAlertController(title: "Fetch failed", message: "There was a problem fetching the list of whistles; please try again: \(error!.localizedDescription)", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self.present(ac, animated: true)
}
}
}
PublicDatabase.add(operation)
completionHandler?()
}
There is indeed a limit of 400 records per fetch operation, so you need to check CKQueryCursor value returned by query completion block, and if it is not nil start another operation with it CKQueryOperation(cursor: cursor).
So in principle your 2nd approach is correct. My guess is that your problem occurs due to threading issue, try removing DispatchQueue.main.async(execute: { () -> Void in.
P.S. Check out how it is done in RxCloudKit (which handles big fetches automatically), it definitely works for fetching 1000+ records - RecordFetcher.queryCompletionBlock(cursor: CKQueryCursor?, error: Error?)
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)
}
I'm learning to use CloudKit and finally managed to retrieve/query data hard coded into the dashboard. I'm trying to query only one value from a record using a CKQueryOperation but I always receive all the values in each and every record.
In my code below I'm trying to get just the "age" values by using the desiredKey property - what I get back is "age", "firstName", "lastName" etc etc.
let container = CKContainer.defaultContainer()
let publicData = container.publicCloudDatabase
// select all records
let predicate = NSPredicate(format: "TRUEPREDICATE", argumentArray: nil)
let query = CKQuery(recordType: "quizRecord", predicate: predicate)
// get just one value only
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["age"]
// addOperation
publicData.addOperation(operation)
publicData.performQuery(query, inZoneWithID: nil) { results, error in
if error == nil { // There is no error
// PROBLEM .. this prints out all values ????
print(results)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
})
}
else {
print(error)
}
}
//// The following / amended code works fine
let container = CKContainer.defaultContainer()
let publicData = container.publicCloudDatabase
// select all records
let predicate = NSPredicate(format: "TRUEPREDICATE", argumentArray: nil)
let query = CKQuery(recordType: "quizRecord", predicate: predicate)
// get just one value only
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["age"]
// get query
operation.recordFetchedBlock = { (record : CKRecord) in
// process record
print(record)
}
// operation completed
operation.queryCompletionBlock = {(cursor, error) in
dispatch_async(dispatch_get_main_queue()) {
if error == nil {
print("no errors")
// code here
} else {
print("error description = \(error?.description)")
}
}
}
// addOperation
publicData.addOperation(operation)
The problem is simple. You set the desiredKeys on the CKQueryOperation but you never actually use the operation. You instead use performQuery on the database.
If you to use the operation and limit the data to the specific keys, you need to add the operation to the database (using addOperation:) instead of calling performQuery. And of course you need to assign a block to the one or more of the recordFetchedBlock and queryCompletionBlock properties of the operation.
let container = CKContainer.defaultContainer()
let publicData = container.publicCloudDatabase
// select all records
let predicate = NSPredicate(format: "TRUEPREDICATE", argumentArray: nil)
let query = CKQuery(recordType: "quizRecord", predicate: predicate)
// get just one value only
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["age"]
operation.recordFetchBlock = { (record : CKRecord) in
// process record
}
operation.queryCompletionBlock = { (cursor : CKQueryCursor!, error : NSError!) in
// Query finished
}
// addOperation
publicData.addOperation(operation)
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"]
}
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()
}
}
})
}