Skip to content
All essays
iOSMarch 27, 202514 min

Swift iCloud Sync: CloudKit Integration

Implement iCloud sync with CloudKit. Sync data across devices and handle conflicts.

Ü
Ümit Uz
Mobile & Full Stack Developer

CloudKit enables data synchronization across users' devices. Learn to implement iCloud sync in iOS apps.

CloudKit Setup

Enable CloudKit

  1. 1Go to Project Settings > Signing & Capabilities
  2. 2Add "iCloud" capability
  3. 3Enable "CloudKit"
  4. 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

  1. 1Error Handling: Handle CloudKit errors gracefully
  2. 2Conflict Resolution: Implement smart conflict resolution
  3. 3Caching: Cache data locally
  4. 4Batching: Use batch operations for efficiency
  5. 5Testing: Test thoroughly with CloudKit
  6. 6Fallback: Provide offline support
  7. 7Privacy: Respect user privacy
  8. 8Monitoring: Monitor CloudKit usage

CloudKit provides seamless data synchronization across devices!

Related essays

Next essay
Swift App Distribution: TestFlight and App Store