import CloudKit
import Combine
class CloudKitManager: ObservableObject {
static let shared = CloudKitManager()
private let container: CKContainer
private let publicDatabase: CKDatabase
private let privateDatabase: CKDatabase
@Published var isSignedIn = false
private init() {
container = CKContainer.default()
publicDatabase = container.publicCloudDatabase
privateDatabase = container.privateCloudDatabase
checkAccountStatus()
}
func checkAccountStatus() {
container.accountStatus { [weak self] status, error in
DispatchQueue.main.async {
self?.isSignedIn = status == .available
}
}
}
// MARK: - Save Record
func savePost(title: String, body: String) async throws -> CKRecord {
let record = CKRecord(recordType: "Post")
record["title"] = title
record["body"] = body
record["createdAt"] = Date()
return try await privateDatabase.save(record)
}
// MARK: - Fetch Records
func fetchPosts() async throws -> [CKRecord] {
let query = CKQuery(recordType: "Post", predicate: NSPredicate(value: true))
query.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: false)]
let (results, _) = try await privateDatabase.records(matching: query)
return results.compactMap { _, result in
try? result.get()
}
}
// MARK: - Query with Predicate
func searchPosts(query: String) async throws -> [CKRecord] {
let predicate = NSPredicate(format: "title CONTAINS %@", query)
let ckQuery = CKQuery(recordType: "Post", predicate: predicate)
let (results, _) = try await privateDatabase.records(matching: ckQuery)
return results.compactMap { _, result in
try? result.get()
}
}
// MARK: - Update Record
func updatePost(record: CKRecord, title: String, body: String) async throws {
record["title"] = title
record["body"] = body
record["updatedAt"] = Date()
try await privateDatabase.save(record)
}
// MARK: - Delete Record
func deletePost(recordID: CKRecord.ID) async throws {
try await privateDatabase.deleteRecord(withID: recordID)
}
// MARK: - Subscribe to Changes
func subscribeToPostChanges() async throws {
let subscription = CKQuerySubscription(
recordType: "Post",
predicate: NSPredicate(value: true),
options: [.firesOnRecordCreation, .firesOnRecordUpdate, .firesOnRecordDeletion]
)
let notification = CKSubscription.NotificationInfo()
notification.shouldSendContentAvailable = true
subscription.notificationInfo = notification
try await privateDatabase.save(subscription)
}
// MARK: - Handle Push Notification
func handleNotification(_ notification: CKNotification) {
guard let queryNotification = notification as? CKQueryNotification else { return }
switch queryNotification.queryNotificationReason {
case .recordCreated:
print("New record created")
case .recordUpdated:
print("Record updated")
case .recordDeleted:
print("Record deleted")
@unknown default:
break
}
}
}
// MARK: - SwiftUI Integration
struct CloudKitPostsView: View {
@StateObject private var cloudKit = CloudKitManager.shared
@State private var posts: [CKRecord] = []
@State private var isLoading = false
var body: some View {
List(posts, id: \.recordID) { record in
VStack(alignment: .leading) {
Text(record["title"] as? String ?? "")
.font(.headline)
Text(record["body"] as? String ?? "")
.font(.subheadline)
}
}
.task {
await loadPosts()
}
}
private func loadPosts() async {
isLoading = true
do {
posts = try await cloudKit.fetchPosts()
} catch {
print("Error loading posts: \(error)")
}
isLoading = false
}
}
CloudKit syncs app data across a user's devices via iCloud. Public databases share data with all users, private databases store user-specific data, and shared databases enable collaboration. I define record types with fields in the CloudKit Dashboard, then create CKRecord instances in code. CKContainer provides access to databases, and operations like CKQuery fetch records. Subscriptions push changes to devices. CloudKit JS enables web access. For simple key-value storage, NSUbiquitousKeyValueStore syncs UserDefaults-like data. Core Data with CloudKit integration handles complex models automatically. CloudKit requires iCloud capability and user authentication. Error handling addresses network issues and conflicts.