ggruen/cloudkitsyncmonitor
`CloudKitSyncMonitor` is a Swift package that listens to notifications sent out by `NSPersistentCloudKitContainer` and translates them into published properties, providing your app with real-time sync state information.
Features and Behavior π
Core Functionality π οΈ
- π‘ Monitors sync status by intercepting and interpreting
NSPersistentCloudKitContainernotifications - π§ Intelligently assesses sync health by considering both network availability and iCloud account status
- π Exposes a
SyncMonitorclass, conveniently accessible via theSyncMonitor.defaultsingleton
Notification Subscriptions π¬
SyncMonitor actively subscribes to notifications from key system components:
- π
NSPersistentCloudKitContainer: For core sync event monitoring - βοΈ
CKContainer: To track CloudKit-specific states - π
NWPathMonitor: For network status updates
Important: β οΈ To ensure accurate and timely state information, call SyncMonitor.default.startMonitoring() as early as possible in your app's lifecycle, preferably in your app delegate or initial view.
Information Levels π
SyncMonitor provides sync information at two distinct levels of granularity:
Top Level
The syncStateSummary property offers a high-level enum summarizing the overall sync state. This is ideal for quick status checks and user-facing information.
Detailed Level
SyncMonitor tracks the states of NSPersistentCloudKitContainer's three primary event types: - Setup: Initialization of the sync environment - Import: Incoming data from CloudKit to the local store - Export: Outgoing data from the local store to CloudKit
To monitor these events, SyncMonitor provides corresponding properties: - setupState: Tracks the state of the setup event - importState: Monitors the state of the import event - exportState: Follows the state of the export event
These properties provide comprehensive information about each sync phase, including convenience methods for extracting commonly needed details.
Problem Detection π¨
SyncMonitor offers robust tools for identifying sync issues:
General Detection
- π΄
hasSyncError: A Boolean indicating the presence of any sync-related error - π‘
isNotSyncing: Detects scenarios where sync should be operational but isn't functioning as expected
Specific Error Information
setupError: Captures issues during the sync setup phaseimportError: Identifies problems with data import from CloudKitexportError: Highlights issues when exporting data to CloudKit
Special Properties π
The isNotSyncing property is particularly useful for detecting subtle sync issues:
- It indicates when setup has completed successfully, but no import event has started, and no errors have been reported
- This can reveal edge cases like OS-level password re-entry prompts, where CloudKit considers the account "available", but
NSPersistentCloudKitContaineris unable to initiate sync - Like other properties, it factors in network availability and iCloud account status for accurate reporting
Importance of Error Detection β οΈ
Timely and accurate error detection is crucial for maintaining data integrity and user trust:
- π‘οΈ Prevents potential data loss by identifying sync failures before they lead to conflicts or data divergence
- β‘ Enables immediate detection and reporting of sync anomalies, often before users notice any issues
- π Significantly enhances user experience by providing transparent, real-time sync status information
- π Helps maintain app reliability and data consistency across devices
Detailed Sync Information π
The setupState, importState, and exportState properties offer comprehensive insights into the sync process:
- Current state of each event type (not started, in progress, succeeded, or failed)
- Precise start and end times for each sync event
- Detailed error information when applicable
Example usage for displaying detailed sync status:
fileprivate var dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .short
return dateFormatter
}()
print("Setup state: \(stateText(for: SyncMonitor.default.setupState))")
print("Import state: \(stateText(for: SyncMonitor.default.importState))")
print("Export state: \(stateText(for: SyncMonitor.default.exportState))")
func stateText(for state: SyncMonitor.SyncState) -> String {
switch state {
case .notStarted:
return "Not started"
case .inProgress(started: let date):
return "In progress since \(dateFormatter.string(from: date))"
case let .succeeded(started: _, ended: endDate):
return "Succeeded at \(dateFormatter.string(from: endDate))"
case let .failed(started: _, ended: endDate, error: _):
return "Failed at \(dateFormatter.string(from: endDate))"
}
}For more detailed information on all available properties and methods, please refer to the comprehensive SyncMonitor documentation.
Usage Examples π
Handle Errors
private let syncMonitor = SyncMonitor.default
if syncMonitor.hasSyncError {
if let error = syncMonitor.setupError {
print("Unable to set up iCloud sync, changes won't be saved! \(error.localizedDescription)")
}
if let error = syncMonitor.importError {
print("Import is broken: \(error.localizedDescription)")
}
if let error = syncMonitor.exportError {
print("Export is broken - your changes aren't being saved! \(error.localizedDescription)")
}
} else if syncMonitor.isNotSyncing {
print("Sync should be working, but isn't. Look for a badge on Settings or other possible issues.")
}Display Error Status
import SwiftUI
import CloudKitSyncMonitor
struct SyncStatusView: View {
@StateObject private var syncMonitor = SyncMonitor.default
var body: some View {
if syncMonitor.syncStateSummary.isBroken {
Image(systemName: syncMonitor.syncStateSummary.symbolName)
.foregroundColor(syncMonitor.syncStateSummary.symbolColor)
}
}
}Display Current Sync State
import SwiftUI
import CloudKitSyncMonitor
struct SyncStatusView: View {
@StateObject private var syncMonitor = SyncMonitor.default
var body: some View {
Image(systemName: syncMonitor.syncStateSummary.symbolName)
.foregroundColor(syncMonitor.syncStateSummary.symbolColor)
}
}Conditional Display
if syncMonitor.syncStateSummary.isBroken || syncMonitor.syncStateSummary.isInProgress {
Image(systemName: syncMonitor.syncStateSummary.symbolName)
.foregroundColor(syncMonitor.syncStateSummary.symbolColor)
}Check for Specific States
if case .accountNotAvailable = syncMonitor.syncStateSummary {
Text("Hey, log into your iCloud account if you want to sync")
}Installation π¦
Swift Package Manager
Add the following to your Package.swift:
dependencies: [
.package(url: "https://github.com/ggruen/CloudKitSyncMonitor.git", from: "3.0.0"),
],
targets: [
.target(
name: "MyApp", // Where "MyApp" is the name of your app
dependencies: ["CloudKitSyncMonitor"]),
]Xcode
- Select File Β» Swift Packages Β» Add Package Dependency...
- Enter the repository URL:
https://github.com/ggruen/CloudKitSyncMonitor.git - Choose "Up to next major version" with
3.0.0as the minimum version.
Development π οΈ
- π΄ Fork repository
- π₯ Check out on your development system
- π Drag the folder this README is in (CloudKitSyncMonitor) into your Xcode project or workspace. This will make Xcode choose the
local version over the version in the package manager.
- π§ If you haven't added it via File > Swift Packages already, go into your project > General tab > Frameworks, Libraries and Embedded Content,
and click the + button to add it. You may need to quit and re-start Xcode to make the new package appear in the list so you can select it.
- ποΈ Make your changes, commit and push to Github
- π Submit pull request
To go back to using the github version, just remove CloudKitSyncMonitor (click on it, hit the delete key, choose to remove reference) from the side bar - Xcode should fall back to the version you added using the Installation instructions above. If you haven't installed it as a package dependency yet, then just delete it from the side bar and then add it as a package dependency using the Installation instructions above.
You can also submit issues if you find bugs, have suggestions or questions, etc. ππ‘β
Package Metadata
Repository: ggruen/cloudkitsyncmonitor
Default branch: main
README: README.md