CloudKit enables data synchronization across users' devices. Learn to implement iCloud sync in iOS apps.
CloudKit Setup
Enable CloudKit
- 1Go to Project Settings > Signing & Capabilities
- 2Add "iCloud" capability
- 3Enable "CloudKit"
- 4Create containers in CloudKit Dashboard
Initialize CloudKit
swift
import CloudKit
class CloudKitManager: ObservableObject {
private let container: CKContainer
init() {
self.container = CKContainer.default()
}
private var publicDatabase: CKDatabase {
container.publicCloudDatabase
}
private var privateDatabase: CKDatabase {
container.privateCloudDatabase
}
private var sharedDatabase: CKDatabase {
container.sharedCloudDatabase
}
}Basic CRUD Operations
Save Data
swift
extension CloudKitManager {
func saveRecord(record: CKRecord, to database: CKDatabase) async throws {
try await database.save(record)
}
func saveItem(name: String, quantity: Int) async throws {
let record = CKRecord(recordType: "Item")
record["name"] = name
record["quantity"] = quantity
try await saveRecord(record: record, to: privateDatabase)
}
}Fetch Data
swift
extension CloudKitManager {
func fetchRecords(recordType: String, from database: CKDatabase) async throws -> [CKRecord] {
let query = CKQuery(recordType: recordType, predicate: NSPredicate(value: true))
let (results, _) = try await database.records(matching: query)
return results.matchingResults.map { $0.1 }
}
func fetchItems() async throws -> [CKRecord] {
return try await fetchRecords(recordType: "Item", from: privateDatabase)
}
}Update Data
swift
extension CloudKitManager {
func updateRecord(_ record: CKRecord, in database: CKDatabase) async throws {
try await database.save(record)
}
func updateItem(_ record: CKRecord, name: String) async throws {
record["name"] = name
try await updateRecord(record, in: privateDatabase)
}
}Delete Data
swift
extension CloudKitManager {
func deleteRecord(_ recordID: CKRecord.ID, from database: CKDatabase) async throws {
try await database.deleteRecord(withID: recordID)
}
func deleteItem(_ record: CKRecord) async throws {
try await deleteRecord(record.recordID, from: privateDatabase)
}
}Query Operations
Filtered Queries
swift
extension CloudKitManager {
func fetchItemsWithName(_ name: String) async throws -> [CKRecord] {
let predicate = NSPredicate(format: "name == %@", name)
let query = CKQuery(recordType: "Item", predicate: predicate)
let (results, _) = try await privateDatabase.records(matching: query)
return results.matchingResults.map { $0.1 }
}
func fetchItemsWithQuantityGreaterThan(_ threshold: Int) async throws -> [CKRecord] {
let predicate = NSPredicate(format: "quantity > %d", threshold)
let query = CKQuery(recordType: "Item", predicate: predicate)
let (results, _) = try await privateDatabase.records(matching: query)
return results.matchingResults.map { $0.1 }
}
}Real-time Sync
Subscribe to Changes
swift
extension CloudKitManager {
func subscribeToChanges() async throws {
let subscription = CKQuerySubscription(
recordType: "Item",
predicate: NSPredicate(value: true),
options: .firesOnRecordCreation
)
let notificationInfo = CKSubscription.NotificationInfo()
notificationInfo.shouldSendContentAvailable = true
subscription.notificationInfo = notificationInfo
try await privateDatabase.save(subscription)
}
func handleRemoteNotification(_ userInfo: [AnyHashable: Any]) async {
guard let notification = CKNotification(fromRemoteNotificationDictionary: userInfo) else {
return
}
if notification.subscriptionID == "com.yourapp.subscription" {
// Handle subscription notification
await refreshData()
}
}
private func refreshData() async {
// Refresh local data from CloudKit
print("Refreshing data from CloudKit")
}
}Conflict Resolution
Handle Conflicts
swift
extension CloudKitManager {
func saveWithConflictResolution(_ record: CKRecord) async throws {
var recordToSave = record
while true {
do {
try await privateDatabase.save(recordToSave)
return
} catch let error as CKError {
guard case .serverRecordChanged = error else {
throw error
}
// Fetch server record
let serverRecord = try await privateDatabase.record(for: record.recordID)
// Resolve conflict
recordToSave = resolveConflict(local: record, server: serverRecord)
}
}
}
private func resolveConflict(local: CKRecord, server: CKRecord) -> CKRecord {
// Custom conflict resolution logic
let resolved = local
// Example: Use newer modification date
if let serverModDate = server.modificationDate,
let localModDate = local.modificationDate,
serverModDate > localModDate {
// Use server values
resolved["name"] = server["name"]
}
return resolved
}
}Batch Operations
Batch Save
swift
extension CloudKitManager {
func saveRecords(_ records: [CKRecord]) async throws {
let operation = CKModifyRecordsOperation(
recordsToSave: records,
recordIDsToDelete: []
)
operation.modifyRecordsCompletionBlock = { _, _, error in
if let error = error {
print("Batch save error: \(error)")
}
}
try await privateDatabase(operation)
}
private func privateDatabase(_ operation: CKDatabaseOperation) async throws {
await withCheckedThrowingContinuation { continuation in
operation.database = privateDatabase
let queue = OperationQueue()
queue.addOperation(operation)
operation.completionBlock = {
if let error = operation.error {
continuation.resume(throwing: error)
} else {
continuation.resume()
}
}
}
}
}Sync Status
Track Sync Status
swift
class SyncManager: ObservableObject {
@Published var syncStatus: SyncStatus = .idle
enum SyncStatus {
case idle
case syncing
case success
case error(String)
}
func syncData() async {
syncStatus = .syncing
do {
// Perform sync
try await CloudKitManager.shared.syncItems()
syncStatus = .success
} catch {
syncStatus = .error(error.localizedDescription)
}
}
}User Detection
Identify Users
swift
extension CloudKitManager {
func fetchUserRecordID() async throws -> CKRecord.ID {
return try await container.userRecordID()
}
func discoverUserIdentity(email: String) async throws {
let emailDiscovered = try await container.discoverUserIdentity(
withEmailAddress: email
)
if let userRecordID = emailDiscovered.userRecordID {
print("User record ID: \(userRecordID.recordName)")
}
}
func discoverAllIdentities() async throws {
let identities = try await container.discoverAllUserIdentities()
for identity in identities {
print("Discovered: \(identity)")
}
}
}Best Practices
- 1Error Handling: Handle CloudKit errors gracefully
- 2Conflict Resolution: Implement smart conflict resolution
- 3Caching: Cache data locally
- 4Batching: Use batch operations for efficiency
- 5Testing: Test thoroughly with CloudKit
- 6Fallback: Provide offline support
- 7Privacy: Respect user privacy
- 8Monitoring: Monitor CloudKit usage
CloudKit provides seamless data synchronization across devices!