CloudKit: Fetch all records with a certain record type?

cloudkit dashboard
cloudkit tutorial
cloudkit update existing record
ckqueryoperation
cloudkit query
nspredicate cloudkit swift
swiftui cloudkit

I have currently got CloudKit set up in my app so that I am adding a new record using the help of the following code below,

CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:@"stringArray"];
CKRecord *record = [[CKRecord alloc] initWithRecordType:@"Strings" recordID:recordID];
[record setObject:[NSArray arrayWithObjects:@"one", @"two", @"three", @"four", nil] forKey:@"stringArray"];
[_privateDatabase saveRecord:record completionHandler:nil];

However, now I would like to be able to fetch ALL records that are of the same record type, "Strings," and return those compiled into an NSArray. How would I go about doing that? Currently, all I have figured out is how to fetch each record individually, using a recordID, which is a hassle, there must be an easier way.

[_privateDatabase fetchRecordWithID:recordID completionHandler:^(CKRecord *record, NSError *error) {
   if (error) {
      // Error handling for failed fetch from private database
   }
   else {
      NSLog(@"ICLOUD TEST: %@", [record objectForKey:@"stringArray"]);            
  }
}];

Aaaand, I've got it. Using the code below, I was able to create a query to run on the database, to then return an NSArray in the completion block, which I looped through, and returned the value for the saved key in an NSLog.

NSPredicate *predicate = [NSPredicate predicateWithValue:YES];
CKQuery *query = [[CKQuery alloc] initWithRecordType:@"Strings" predicate:predicate];

[_privateDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
    for (CKRecord *record in results) {
        NSLog(@"Contents: %@", [record objectForKey:@"stringArray"]);
    }
}];

How do I fetch all CloudKit Record…, How do I fetch all CloudKit Records created by the current user? Say I have a ' Species' Record Type that contains Public Records which were created by a� Similar to the save Records method, if you don’t specify a zone ID in the options parameter, the records are fetched from the default zone. To fetch records from a specific zone, pass a dictionary with the zone ID key set to the name of your zone.

Solution for Swift 4, shows how to fetch all the records of type "YourTable", also prints System Field and Custom Field:

let query = CKQuery(recordType: "YourTable", predicate: NSPredicate(value: true))
CKContainer.default().publicCloudDatabase.perform(query, inZoneWith: nil) { (records, error) in
  records?.forEach({ (record) in

    // System Field from property
    let recordName_fromProperty = record.recordID.recordName
    print("System Field, recordName: \(recordName_fromProperty)")

    // Custom Field from key path (eg: deeplink)
    let deeplink = record.value(forKey: "deeplink")
    print("Custom Field, deeplink: \(deeplink ?? "")")
  })
}

Fetching Records, Fetching Records. After you save records to the database, you can retrieve them using different mechanisms. Fetch individual records by record� If you use the Location field type, you can also fetch records within a geographical region, as described in Fetch Records by Location. Fetch Records by Identifier. If you know the record IDs for the records you want to fetch, then you can fetch by individual record ID. For example, this code fragment fetches a record named 115.

Here's the answer in Swift 3.0.

func myQuery()  {
    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: "tableName", predicate: predicate)

    publicDatabase.perform(query, inZoneWith: nil) { (record, error) in

        for record: CKRecord in record! {
            //...

            // if you want to access a certain 'field'.
            let name = record.value(forKeyPath: "Name") as! String                
        }
    }
}

How do I fetch all CloudKit Records created by the current user , Then you make a new record type for species that now includes some sort of id for the user, and then the predicate would look for that specific id. Fetching all records of a specific type. This is the most basic one. To run a query to get all recipe records from the database, the first step is to construct a query with the record type and a predicate. Since all records should be retrieved, a predicate with the value true can be used.

The followig function will return ALL records for requested record type:

let database = CKContainer(identifier: "container_here").privateCloudDatabase
typealias RecordsErrorHandler = ([CKRecord], Swift.Error?) -> Void

func fetchRecords(forType type: String, completion: RecordsErrorHandler? = nil) {

    var records = [CKRecord]()

    let query = CKQuery(recordType: type, predicate: NSPredicate(value: true))
    let queryOperation = CKQueryOperation(query: query)
    queryOperation.zoneID = CloudAssistant.shared.zone.zoneID

    queryOperation.recordFetchedBlock = { record in
        records.append(record)
    }

    queryOperation.queryCompletionBlock = { cursor, error in

        self.fetchRecords(with: cursor, error: error, records: records) { records in
            completion?(records, nil)
        }
    }

    database.add(queryOperation)
}

private func fetchRecords(with cursor: CKQueryCursor?, error: Swift.Error?, records: [CKRecord], completion: RecordsHandler?) {

    var currentRecords = records

    if let cursor = cursor, error == nil {

        let queryOperation = CKQueryOperation(cursor: cursor)

        queryOperation.recordFetchedBlock = { record in
            currentRecords.append(record)
        }

        queryOperation.queryCompletionBlock = { cursor, error in
            self.fetchRecords(with: cursor, error: error, records: currentRecords, completion: completion)
        }

        database.add(queryOperation)

    } else {
        completion?(records)
    }
}

Working with CloudKit records: CKRecord.Reference, fetch , Reference let us query to find all suggestions for a specific whistle, but it has another func add(suggestion: String) { let whistleRecord = CKRecord( recordType:� However, now I would like to be able to fetch ALL records that are of the same record type, "Strings," and return those compiled into an NSArray. How would I go about doing that? Currently, all I have figured out is how to fetch each record individually, using a recordID, which is a hassle, there must be an easier way.

Swift 5

After looking through a bunch of posts and solutions on SO I have managed to come with a solution that suits my needs and should be simple enough for anyone that just wants to fetch all of their records of given type from iCloud.

Solution

The solution that uses an extension to the CKDatabase to introduce a method that handles the cursor: CKQueryOperation.Cursor of CKQueryOperation to continue asking iCloud for more records. In this approach I dispatch to the background queue so I can block it and wait for the operation to be finished completely, either on receiving an error or with the last batch of records. Once the semaphore unlocks the queue it proceeds with calling the main completion block with the result. Also I am taking advantage of Swift's Result type in the completion handler.

extension CKDatabase {

    func fetchAll(
        recordType: String, resultsLimit: Int = 100, timeout: TimeInterval = 60,
        completion: @escaping (Result<[CKRecord], Error>) -> Void
    ) {
        DispatchQueue.global().async { [unowned self] in
            let query = CKQuery(
                recordType: recordType, predicate: NSPredicate(value: true)
            )
            let semaphore = DispatchSemaphore(value: 0)
            var records = [CKRecord]()
            var error: Error?

            var operation = CKQueryOperation(query: query)
            operation.resultsLimit = resultsLimit
            operation.recordFetchedBlock = { records.append($0) }
            operation.queryCompletionBlock = { (cursor, err) in
                guard err == nil, let cursor = cursor else {
                    error = err
                    semaphore.signal()
                    return
                }
                let newOperation = CKQueryOperation(cursor: cursor)
                newOperation.resultsLimit = operation.resultsLimit
                newOperation.recordFetchedBlock = operation.recordFetchedBlock
                newOperation.queryCompletionBlock = operation.queryCompletionBlock
                operation = newOperation
                self?.add(newOperation)
            }
            self?.add(operation)

            _ = semaphore.wait(timeout: .now() + 60)

            if let error = error {
                completion(.failure(error))
            } else {
                completion(.success(records))
            }
        }
    }

}
Usage

Using the method is fairly straight forward for anyone familiar with Swift's closure syntax and Result type.

let database: CKDatabase = ...
database.fetchAll(recordType: "User") { result in
    switch result {
        case .success(let users):
            // Handle fetched users, ex. save them to the database
        case .failure(let error):
            // Handle Error
        }
    }
}

Reading from iCloud with CloudKit: CKQueryOperation and , But in our case that would mean downloading the audio for every record the genre and user comments, but not the audio – we'll fetch that separately, as needed. whether a class or a struct is the right approach when considering data types, by CloudKit when all records have been downloaded, and will be given two� To receive the latest developer news, visit and subscribe to our News and Updates.

CloudKit 101, Some of CloudKit's features, such as saving related records in batches and sharing can only be Every database on CloudKit comes with a record of type User by default. To fetch the user record, you need to get its ID first. CloudKit provides a way to store data as records in a database that users of your app can share. Record types are used to differentiate between records storing different types of information. Each record is a dictionary of key-value pairs, with each key representing one field of the record.

CloudKit Fetch First Record Only, I'd like to know how to fetch just the first record from a CloudKit table. For example , I have Well, there's 2 things you need for your particular scenario. privateCloudDatabase let query = CKQuery(recordType: "Data", predicate: predicate) privateDatabase. How do I fetch all CloudKit Records created by the current user? Storing Record Metadata. To relate records in your local data store to records on the server, you will likely need to store the metadata for your records (record name, zone ID, change tag, creation date, and so on). There is a handy method on CKRecord, encodeSystemFieldsWithCoder, that helps you do this for system fields. You will still have to

CloudKit: Fetch all records with a certain record type?, However, now I would like to be able to fetch ALL records that are of the same record type, "Strings," and return those compiled into an NSArray� Go ahead and run the app now, then submit a suggestion for your whistle. Once that's done, go to the CloudKit Dashboard to make sure the record type was created as expected (i.e., that everything works!), then check the Metadata Indexes boxes next to Query for ID and Sort for Date Created, just like you did for Whistles.

Comments
  • According to CKQuery API Reference: "Predicates are based on a format string. You cannot use value or block-based predicates." I don't think it was always like this, but currently neither answer would be correct because they use NSPredicate(value:)
  • I have used it in the past, but anyone doing so should know that it's not supported for CloudKit, according to Apple's own documentation. Apple could very well make changes that would break that code, and it can be done w/out using value or block based predicates.
  • I'd argue that it's akin to identifying enums through hash-values. You can get away with it, until the declaration order changes and your code breaks...essentially, there's a reason why manufacturers include manuals for how to use their api's, etc...
  • this won't return all records though, just a subset (usually limited to 100 by default)
  • @MaxDesiatov what you mean with limited? How to fetch all?
  • Never mind, it (100 limit) is answered here: link and link
  • Does not work. Getting error: <CKError 0x13de5ffc0: "Invalid Arguments" (12/2015); server message = "Field '___recordID' is not marked queryable"
  • this query does not fetch more objects than 100 at a time
  • where is your add function? Thanks.
  • @Emmy I am calling add(_ operation: CKDatabaseOperation) method on CKDatabase