I'm working with CloudKit for the first time and am having trouble executing a CKQueryOperation to query all records of a given type. It doesn't help that Apple has deprecated most of the stuff I've found online and that their documentation for these things are completely blank besides the func declaration. I think I've got the "skeleton" of the code done but am unsure of what goes into the .recordMatchedBlock
and the .queryResultsBlock
.
I have a func queryAllNotes() which should query all records in the public database of type "Notes" and return an array of tuples of the note's title and its associated cloudID, which is just the unique recordName given to it when it is added to the database.
Here's the code for queryAllNotes() :
private func queryAllNotes() -> [(title: String, cloudID: String)] {
/*
query all notes in the cloud DB into an array to populate
the tableView
*/
var resultArray: [(title: String, cloudID: String)] = []
//set the cloud database to .publicCloudDatabase
let container = CKContainer.default()
let cloudDB = container.publicCloudDatabase
let pred = NSPredicate(value: true) //true -> return all records
let query = CKQuery(recordType: "Notes", predicate: pred)
let queryOperation = CKQueryOperation(query: query)
queryOperation.database = cloudDB
queryOperation.resultsLimit = 100
queryOperation.recordMatchedBlock = { (record: CKRecord) in
let noteTitle = record["Title"] as! String
let noteCloudID = record.recordID.recordName
resultArray.append((noteTitle, noteCloudID))
}
queryOperation.queryResultBlock = { (cursor, error) in
}
return resultArray
}
To my understanding the .recordMatchedBlock
is called for every record returned by the query so I think it is complete but I could be very wrong. In regards to the .queryResultBlock
, my understanding is that the query technically only return one record at a time and this block basically tells the query to run again for the next record for all records within the .resultLimit
. How can I structure this query? I am keen to understand what each of these blocks do.
Also this is for a macOS app; I don't know if the code is different for macOS vs iOS but I thought I should include this just in case.
Also I'm getting an error saying "Type of expression is ambiguous without more context" which I'm assuming is because I haven't completed setting up my query. If it's for a different reason could also explain why this is happening.
I call this func inside of viewDidLoad()
like so:
//array var for the array that is used to populate the tableView
var noteRecords: [(title: String, cloudID: String)] = []
override func viewDidLoad() {
super.viewDidLoad()
// do additional setup here
// set serachField delegate
searchField.delegate = self
// set tableView delegate and data source
tableView.delegate = self
tableView.dataSource = self
// load all NoteRecords in public cloud db into noteRecords
noteRecords = queryAllNotes()
}
With the new async
pattern it has become much easier to fetch data from CloudKit.
Instead of CKQueryOperation
you call records(matching:resultsLimit:)
directly and map the result to whatever you like.
A possible error is handed over to the caller.
func queryAllNotes() async throws -> [(title: String, cloudID: String)] {
//set the cloud database to .publicCloudDatabase
let container = CKContainer.default()
let cloudDB = container.publicCloudDatabase
let pred = NSPredicate(value: true) //true -> return all records
let query = CKQuery(recordType: "Notes", predicate: pred)
let (notesResults, _) = try await cloudDB.records(matching: query,
resultsLimit: 100)
return notesResults
.compactMap { _, result in
guard let record = try? result.get(),
let noteTitle = record["Title"] as? String else { return nil }
return (title: noteTitle, cloudID: record.recordID.recordName)
}
}
And use it
override func viewDidLoad() {
super.viewDidLoad()
// do additional setup here
// set serachField delegate
searchField.delegate = self
// set tableView delegate and data source
tableView.delegate = self
tableView.dataSource = self
// load all NoteRecords in public cloud db into noteRecords
Task {
do {
noteRecords = try await queryAllNotes()
tableView.reloadData()
} catch {
print(error)
}
}
}
Please watch the related video from WWDC 2021 for detailed information about the async
CloudKit APIs and also the Apple examples on GitHub.
Side note:
Rather than a tuple use a struct. Tuples as data source array are discouraged.