markbattistella/cloudsynckit
`CloudSyncKit` is a lightweight Swift package for observing `NSPersistentCloudKitContainer` sync events and surfacing the current iCloud sync status in SwiftUI.
Requirements
| Platform | Minimum | |------------|---------| | iOS | 17.0 | | macOS | 14.0 | | tvOS | 17.0 | | watchOS | 10.0 | | visionOS | 1.0 |
Installation
Add CloudSyncKit to your Swift project using Swift Package Manager:
dependencies: [
.package(url: "https://github.com/markbattistella/CloudSyncKit", from: "1.0.0")
]Alternatively, add it in Xcode via File > Add Packages and entering the package repository URL.
Setup
Create a CloudSyncMonitor instance and call start() when your view appears:
@State private var syncMonitor = CloudSyncMonitor()
var body: some View {
ContentView()
.onAppear { syncMonitor.start() }
}Because CloudSyncMonitor is @Observable, you can pass it through the SwiftUI environment or inject it directly into views — SwiftUI will automatically re-render any view that reads syncMonitor.status when the value changes.
Displaying Sync Status
Default view
Pass the current status value to SyncStatusView:
SyncStatusView(status: syncMonitor.status)The view displays an icon and a plain-language description of the current state, with four possible appearances:
| Status | Icon | Label | | ------ | ---- | ----- | | .idle | icloud | iCloud is up to date | | .syncing | arrow.triangle.2.circlepath (animated) | Uploading / Downloading / Setting up | | .success | checkmark.icloud.fill | Sync completed | | .failed | exclamationmark.icloud.fill | Sync failed: \<message\> |
The spinning animation on .syncing is automatically suppressed when the user has enabled Reduce Motion.
Example
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [MyModel]
@State private var syncMonitor = CloudSyncMonitor()
var body: some View {
NavigationStack {
List {
Section {
SyncStatusView(status: syncMonitor.status)
}
Section("Items") {
ForEach(items) { item in
Text(item.name)
}
}
}
.navigationTitle("My App")
}
.onAppear { syncMonitor.start() }
}
}Custom Status Views
CloudSyncKit uses the CloudSyncStatusView protocol so you can provide your own visual design while keeping the same observable-driven data flow.
Conform any SwiftUI View to CloudSyncStatusView by implementing init(status:):
struct CompactSyncIndicator: CloudSyncStatusView {
let status: CloudSyncMonitor.Status
var body: some View {
switch status {
case .idle:
EmptyView()
case .syncing:
ProgressView()
.controlSize(.small)
case .success:
Image(systemName: "checkmark.icloud.fill")
.foregroundStyle(.green)
case .failed(let message):
Label(message, systemImage: "exclamationmark.icloud.fill")
.foregroundStyle(.red)
}
}
}Use it exactly like the built-in view:
CompactSyncIndicator(status: syncMonitor.status)Because the view receives a plain CloudSyncMonitor.Status value, the parent's @Observable observation handles all re-rendering automatically — no special wiring needed inside the custom view.
Monitor Status Values
CloudSyncMonitor.Status is an equatable enum with four cases:
public enum Status: Equatable {
case idle
case syncing(String) // associated message: "Uploading to iCloud" etc.
case success
case failed(String) // associated message: localised error description
}You can read it directly for conditional logic:
if case .failed(let message) = syncMonitor.status {
showErrorBanner(message)
}Things to Note
CloudSyncMonitor.start()is idempotent — calling it multiple times registers the notification observer only once- Sync events are only emitted by
NSPersistentCloudKitContainer; if your stack uses a plainNSPersistentContainer, the monitor will remain in.idleindefinitely - CloudKit requires a valid iCloud account on the device and a correctly provisioned container in App Store Connect
- The monitor must be kept alive for the duration of the session; releasing it removes the observer and stops all updates
Contributing
Contributions are welcome. Please open an Issue or PR for fixes, feature proposals, or documentation improvements.
PR titles should follow the format: YYYY-mm-dd - Title
Licence
CloudSyncKit is released under the MIT licence.
Package Metadata
Repository: markbattistella/cloudsynckit
Default branch: main
README: README.md