Skip to main content

Working with CloudKit

1. Setting Up CloudKit Container

Property Description Value
containerIdentifier Unique identifier for the CloudKit container ck.container.myapp
isPrivate Indicates if the container is private to the user true
isShared Indicates if the container is shared with other users false

Example: Configure CloudKit Container

import CloudKit

let container = CKContainer(identifier: "com.myapp.container")
let publicDatabase = container.publicCloudDatabase
Note: Ensure the container identifier matches your app's CloudKit configuration in Xcode.

2. Using Public and Private Databases

Database Type Description Access Level Usage Example
CKDatabaseType.public Shared database accessible to all users of the app. Public let publicDatabase = CKContainer.default().publicCloudDatabase
CKDatabaseType.private Private database accessible only to the current user. Private let privateDatabase = CKContainer.default().privateCloudDatabase
CKDatabaseType.shared Shared database accessible to users in the same container. Shared let sharedDatabase = CKContainer.default().sharedCloudDatabase

Example: Saving to Public Database

let record = CKRecord(recordType: "User")
record["name"] = "John Doe"
record["email"] = "john@example.com"

let publicDatabase = CKContainer.default().publicCloudDatabase
publicDatabase.save(record) { (savedRecord, error) in
    if let error = error {
        print("Error saving record: " + error.localizedDescription)
    } else {
        print("Record saved successfully")
    }
}

Example: Fetching from Private Database

let privateDatabase = CKContainer.default().privateCloudDatabase
let query = CKQuery(recordType: "User", predicate: NSPredicate(value: true))

privateDatabase.perform(query, inZoneWith: nil) { (records, error) in
    if let error = error {
        print("Error fetching records: " + error.localizedDescription)
    } else if let records = records {
        print("Fetched " + String(records.count) + " records")
    }
}
Note: Public databases are accessible to all users of the app, while private databases are only accessible to the current user.

3. Creating CKRecord Objects

Property Description Example
recordType String identifier for the record type "User"
recordID Unique identifier for the record (optional) CKRecord.ID(recordName: "user123")
values Dictionary of key-value pairs for record data ["name": "John Doe", "email": "john@example.com"]

Example: Creating a CKRecord

import CloudKit

let record = CKRecord(recordType: "User")
record["name"] = "John Doe"
record["email"] = "john@example.com"
record["age"] = 30
Note: CKRecord values can be String, Int, Double, Date, Data, CLLocation, or CKAsset.

4. Saving Records to CloudKit

Property Value Description
record CKRecord The record to save to CloudKit
database CKDatabase The database to save the record to (private or public)
completion (CKRecord?, Error?) -> Void Callback when save operation completes

Example: Saving a Record to Public Database

let record = CKRecord(recordType: "Task")
record["title"] = "Buy groceries"
record["completed"] = false

let database = CKContainer.default().publicCloudDatabase
database.save(record) { record, error in
    if let error = error {
        print("Save error: \(error)")
    } else {
        print("Record saved successfully")
    }
}
Note: Ensure the user is authenticated to CloudKit before attempting to save records.
Warning: Saving records without proper error handling may lead to data inconsistencies.

5. Fetching Records with CKQuery

Property Value Description
recordType String The type of record to fetch
predicate NSPredicate? Condition to filter records (optional)
sortDescriptors [NSSortDescriptor]? Sorting criteria for results (optional)

Example: Fetching Records with CKQuery

import CloudKit

let container = CKContainer.default()
let database = container.publicCloudDatabase

let query = CKQuery(recordType: "Photo", predicate: NSPredicate(value: true))

database.perform(query, inZoneWith: nil) { (records, error) in
    if let error = error {
        print("Error fetching records: \(error)")
        return
    }

    guard let records = records else { return }
    for record in records {
        print("Fetched record: \(record.recordID)")
    }
}
Note: CKQuery is used to fetch records from CloudKit databases. It supports filtering with predicates and sorting with sort descriptors.

6. Using Predicates for Filtering

Predicates in CloudKit are used to filter records when fetching data from the database. They allow you to specify conditions that records must meet to be included in the results.

Property Description Example
NSPredicate Class used to define filtering conditions in CloudKit. NSPredicate(format: "name CONTAINS[cd] %@", searchTerm)

Example: Filtering Records by Name

let predicate = NSPredicate(format: "name CONTAINS[cd] %@", "John")
let query = CKQuery(recordType: "User", predicate: predicate)

Example: Filtering Records by Date Range

let startDate = Calendar.current.date(byAdding: .day, value: -7, to: Date())!
let endDate = Date()
let predicate = NSPredicate(format: "creationDate >= %@ AND creationDate <= %@", startDate as CVarArg, endDate as CVarArg)
let query = CKQuery(recordType: "Event", predicate: predicate)

Example: Combining Predicates

let namePredicate = NSPredicate(format: "name CONTAINS[cd] %@", "John")
let statusPredicate = NSPredicate(format: "status == %@", "active")
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [namePredicate, statusPredicate])
let query = CKQuery(recordType: "User", predicate: compoundPredicate)

7. Handling CloudKit Errors

Error Type Description Handling Strategy
CKError Base error type for all CloudKit operations Check errorCode to determine specific error
CKErrorNetworkFailure Network connectivity issues Retry operation or notify user of offline state
CKErrorNotAuthenticated User not logged into iCloud Prompt user to sign in to iCloud
CKErrorServiceUnavailable CloudKit service temporarily unavailable Implement exponential backoff retry logic
CKErrorZoneBusy Zone is being modified by another operation Wait and retry after a delay
CKErrorQuotaExceeded User has exceeded storage limits Inform user and guide them to iCloud settings

Example: Error Handling in CloudKit Operation

let database = CKContainer.default().publicCloudDatabase
let record = CKRecord(recordType: "Task")
database.save(record) { (savedRecord, error) in
    if let error = error {
        switch error {
        case CKError.networkFailure:
            print("Network issue, retry later")
        case CKError.notAuthenticated:
            print("User not logged into iCloud")
        default:
            print("CloudKit error: \(error.localizedDescription)")
        }
    } else {
        print("Record saved successfully")
    }
}
Note: Always handle CKError.networkFailure gracefully by implementing retry logic with exponential backoff to avoid overwhelming the server.
Warning: Do not silently ignore CKError.notAuthenticated — prompt the user to enable iCloud or sign in.

8. Subscribing to Record Changes

CloudKit allows you to subscribe to changes in records to receive real-time updates. Subscriptions can be configured for specific record types or individual records.

Property Description Values
recordType Specifies the record type to subscribe to String
subscriptionID Unique identifier for the subscription String
notificationInfo Configuration for push notifications CKSubscription.NotificationInfo

Example: Creating a Record Change Subscription

import CloudKit

let subscription = CKQuerySubscription(
    recordType: "User",
    predicate: NSPredicate(value: true),
    subscriptionID: "user-changes",
    options: .firesOnRecordCreation
)

let notificationInfo = CKSubscription.NotificationInfo(
    alertBody: "A user record was updated",
    shouldSendPushNotification: true
)

subscription.notificationInfo = notificationInfo

CKContainer.default().publicCloudDatabase.save(
    subscription,
    completionHandler: { (savedSubscription, error) in
        if let error = error {
            print("Error saving subscription: \(error)")
        } else {
            print("Subscription saved successfully")
        }
    }
)
Note: Subscriptions must be saved to the CloudKit database to take effect. Notifications are only sent for records that match the predicate.

9. Using CKShare for Sharing

CloudKit's CKShare enables sharing of records and assets between users. It allows developers to create shareable content that can be accessed by other iCloud users.

Property Type Description
record CKRecord? The record to be shared. Must not be nil.
container CKContainer The container that holds the record to be shared.
participant CKShare.Participant? The participant to whom the share is sent. Can be nil for default behavior.

Example: Creating a CKShare

let share = CKShare(record: record, container: container)
share.title = "Shared Record"
share.message = "Check out this shared record!"

Example: Saving a CKShare

let database = CKContainer.default().publicCloudDatabase
database.save(share) { (savedShare, error) in
    if let error = error {
        print("Error saving share: \(error)")
    } else {
        print("Share saved successfully")
    }
}
Note: The CKShare must be saved to the public database to be accessible by other users.
Warning: Ensure that the record being shared is accessible to other users. If the record is private, it will not be visible to others.

10. Syncing with Core Data (NSPersistentCloudKitContainer)

CloudKit integration with Core Data is facilitated through NSPersistentCloudKitContainer, which enables automatic synchronization of Core Data records with CloudKit.

Property Description Notes
containerIdentifier Unique identifier for the CloudKit container Must match the identifier in your CloudKit dashboard
name Name of the persistent container Used to locate the container in the app’s bundle

Example: Setting up CloudKit Container

import CoreData

let persistentContainer = NSPersistentCloudKitContainer(name: "MyAppModel")
persistentContainer.loadPersistentStores { _, error in
    if let error = error {
        print("Core Data error: \(error)")
    }
}
Note: Ensure that your app’s entitlements include the CloudKit capability and that the container identifier matches between your code and CloudKit dashboard.

Example: Fetching Data from CloudKit

let context = persistentContainer.newBackgroundContext()
let fetchRequest: NSFetchRequest<MyEntity> = MyEntity.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "isSynced == %@", NSNumber(value: true))

do {
    let results = try context.fetch(fetchRequest)
    // Process fetched entities
} catch {
    print("Fetch error: \(error)")
}
Warning: CloudKit sync is asynchronous and may introduce delays in data availability. Always handle potential staleness in UI updates.

Was this article helpful?