Contents

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 plain NSPersistentContainer, the monitor will remain in .idle indefinitely
  • 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