Why will my CKQueryOperation only return a Cursor if the results limit is less than 1000?
I believe that 400 is the the limit for a single operation, so you need to use cursor to get more records, and keep on doing that while returned cursor is not nil
.
See how it is done in RxCloudKit library' RecordFetcher
-
https://github.com/maxvol/RxCloudKit/blob/master/RxCloudKit/RecordFetcher.swift
Understanding how to correctly execute CKQueryOperation
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.
CloudKit on iOS won't fetch all of my desired records
I discovered the issue and it's not a CloudKit bug. It's actually something kind of dumb on my part. In my iCloud entitlements file, I had a line that explicitly set the iCloud session to the "Production" environment instead of the "Development" one. I don't know why I had that there; I must have been debugging something in the production public store and just forgot to undo it when I was done.
Regardless though, I think it should still have worked just fine. It may not have fetched ALL of my private records since most of them are in the development environment, but I had a few that I saved to the production environment too, when I was doing the debugging. Also, I don't understand why the iOS Simulator couldn't get records from EITHER of the databases when it was going through the production environment.
But as long as it's working now, that's fine with me.
Swift CloudKit and CKQuery: how to iteratively retrieve records when queryResultBlock returns a query cursor
No need for queryResultBlock
in Swift 5.5.
I use this because my CKRecord
types are always named the same as their Swift counterparts. You can replace recordType: "\(Record.self)"
with your recordType
if you want, instead.
public extension CKDatabase {
/// Request `CKRecord`s that correspond to a Swift type.
///
/// - Parameters:
/// - recordType: Its name has to be the same in your code, and in CloudKit.
/// - predicate: for the `CKQuery`
func records<Record>(
type _: Record.Type,
zoneID: CKRecordZone.ID? = nil,
predicate: NSPredicate = .init(value: true)
) async throws -> [CKRecord] {
try await withThrowingTaskGroup(of: [CKRecord].self) { group in
func process(
_ records: (
matchResults: [(CKRecord.ID, Result<CKRecord, Error>)],
queryCursor: CKQueryOperation.Cursor?
)
) async throws {
group.addTask {
try records.matchResults.map { try $1.get() }
}
if let cursor = records.queryCursor {
try await process(self.records(continuingMatchFrom: cursor))
}
}
try await process(
records(
matching: .init(
recordType: "\(Record.self)",
predicate: predicate
),
inZoneWith: zoneID
)
)
return try await group.reduce(into: [], +=)
}
}
}
Related Topics
How to Import Modules Without an Xcode Project in Swift
Updating a @Published Variable Based on Changes in an Observed Variable
Realitykit - How to Edit or Add a Lighting
Icloud Drive Issue: "[Documentmanager] Failed to Associate Thumbnails for Picked Url"
How to Apply a Context Menu to Buttons in a Swiftui List Row
How to Call Non-Escaping Closure Inside a Local Closure
Creating a Type Bound to a Certain Range in Swift
How Does Let X Where X.Hassuffix("Pepper") Work
Swift Objc_Getassociatedobject Always Nil
How to Make Player Move to Opposite Side While Is in a Path
Swift: Copy Information Selected by User in Abpersonviewcontroller to Dictionary
What Does "Constrain to Margins" Mean in Interface Builder in Xcode 6.0.1
How to Add Floating Button on Top of the Uitableview
Convert to Latest Swift Syntax' Breaks the Build Even When There Are No Changes