Skip to content

Commit c6a17b9

Browse files
Properties for observing the sync state of the engine. (#249)
* Properties for observing the sync state of the engine. * wip * Update Sources/SQLiteData/CloudKit/SyncEngine.swift Co-authored-by: Stephen Celis <[email protected]> --------- Co-authored-by: Stephen Celis <[email protected]>
1 parent 2235c9e commit c6a17b9

File tree

3 files changed

+109
-13
lines changed

3 files changed

+109
-13
lines changed

Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Examples/Reminders/RemindersLists.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,12 +267,17 @@ struct RemindersListsView: View {
267267
}
268268
.onMove(perform: model.move(from:to:))
269269
} header: {
270-
Text("My Lists")
271-
.font(.system(.title2, design: .rounded, weight: .bold))
272-
.foregroundStyle(Color(.label))
273-
.textCase(nil)
274-
.padding(.top, -16)
275-
.padding([.leading, .trailing], 4)
270+
HStack {
271+
Text("My Lists")
272+
if syncEngine.isSynchronizing {
273+
ProgressView().id(UUID())
274+
}
275+
}
276+
.font(.system(.title2, design: .rounded, weight: .bold))
277+
.foregroundStyle(Color(.label))
278+
.textCase(nil)
279+
.padding(.top, -16)
280+
.padding([.leading, .trailing], 4)
276281
}
277282
.listRowInsets(EdgeInsets(top: 8, leading: 12, bottom: 8, trailing: 12))
278283

Sources/SQLiteData/CloudKit/SyncEngine.swift

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
let dataManager = Dependency(\.dataManager)
3636
private let observationRegistrar = ObservationRegistrar()
3737
private let notificationsObserver = LockIsolated<(any NSObjectProtocol)?>(nil)
38+
private let activityCounts = LockIsolated(ActivityCounts())
3839

3940
/// The error message used when a write occurs to a record for which the current user
4041
/// does not have permission.
@@ -357,6 +358,38 @@
357358
public func start() async throws {
358359
try await start().value
359360
}
361+
362+
/// Determines if the sync engine is currently sending local changes to the CloudKit server.
363+
///
364+
/// It is an observable value, which means if it is accessed in a SwiftUI view, or some other
365+
/// observable context, then the view will automatically re-render when the value changes. As
366+
/// such, it can be useful for displaying a progress view to indicate that work is currently
367+
/// being done to synchronize changes.
368+
public var isSendingChanges: Bool {
369+
sendingChangesCount > 0
370+
}
371+
372+
/// Determines if the sync engine is currently processing changes being sent to the device
373+
/// from CloudKit.
374+
///
375+
/// It is an observable value, which means if it is accessed in a SwiftUI view, or some other
376+
/// observable context, then the view will automatically re-render when the value changes. As
377+
/// such, it can be useful for displaying a progress view to indicate that work is currently
378+
/// being done to synchronize changes.
379+
public var isFetchingChanges: Bool {
380+
fetchingChangesCount > 0
381+
}
382+
383+
/// Determines if the sync engine is currently sending or receiving changes from CloudKit.
384+
///
385+
/// This value is true if either of ``isSendingChanges`` or ``isFetchingChanges`` is true.
386+
/// It is an observable value, which means if it is accessed in a SwiftUI view, or some other
387+
/// observable context, then the view will automatically re-render when the value changes. As
388+
/// such, it can be useful for displaying a progress view to indicate that work is currently
389+
/// being done to synchronize changes.
390+
public var isSynchronizing: Bool {
391+
isSendingChanges || isFetchingChanges
392+
}
360393

361394
/// Stops the sync engine if it is running.
362395
///
@@ -399,7 +432,7 @@
399432
try SQLiteSchema
400433
.where {
401434
$0.type.eq(#bind(.table))
402-
&& $0.tableName.in(tables.map { $0.base.tableName })
435+
&& $0.tableName.in(tables.map { $0.base.tableName })
403436
}
404437
.fetchAll(db)
405438
return try namesAndSchemas.compactMap { schema -> RecordType? in
@@ -737,6 +770,29 @@
737770
public static func isSynchronizingChanges() -> some QueryExpression<Bool> {
738771
$syncEngineIsSynchronizingChanges()
739772
}
773+
774+
private var sendingChangesCount: Int {
775+
get {
776+
observationRegistrar.access(self, keyPath: \.isSendingChanges)
777+
return activityCounts.withValue(\.sendingChangesCount)
778+
}
779+
set {
780+
observationRegistrar.withMutation(of: self, keyPath: \.isSendingChanges) {
781+
activityCounts.withValue { $0.sendingChangesCount = newValue }
782+
}
783+
}
784+
}
785+
private var fetchingChangesCount: Int {
786+
get {
787+
observationRegistrar.access(self, keyPath: \.isFetchingChanges)
788+
return activityCounts.withValue(\.fetchingChangesCount)
789+
}
790+
set {
791+
observationRegistrar.withMutation(of: self, keyPath: \.isFetchingChanges) {
792+
activityCounts.withValue { $0.fetchingChangesCount = newValue }
793+
}
794+
}
795+
}
740796
}
741797

742798
extension PrimaryKeyedTable {
@@ -814,9 +870,22 @@
814870
failedRecordDeletes: failedRecordDeletes,
815871
syncEngine: syncEngine
816872
)
817-
case .willFetchRecordZoneChanges, .didFetchRecordZoneChanges, .willFetchChanges,
818-
.didFetchChanges, .willSendChanges, .didSendChanges:
819-
break
873+
874+
case .willFetchRecordZoneChanges:
875+
fetchingChangesCount += 1
876+
case .didFetchRecordZoneChanges:
877+
fetchingChangesCount -= 1
878+
879+
case .willFetchChanges:
880+
fetchingChangesCount += 1
881+
case .didFetchChanges:
882+
fetchingChangesCount -= 1
883+
884+
case .willSendChanges:
885+
sendingChangesCount += 1
886+
case .didSendChanges:
887+
sendingChangesCount -= 1
888+
820889
@unknown default:
821890
break
822891
}
@@ -2054,8 +2123,7 @@
20542123
tablesByName: [String: any SynchronizableTable]
20552124
) throws -> [String: Int] {
20562125
let tableDependencies = try userDatabase.read { db in
2057-
var dependencies:
2058-
[HashableSynchronizedTable: [any SynchronizableTable]] = [:]
2126+
var dependencies: [HashableSynchronizedTable: [any SynchronizableTable]] = [:]
20592127
for table in tables {
20602128
func open<T>(_: some SynchronizableTable<T>) throws -> [String] {
20612129
try PragmaForeignKeyList<T>.select(\.table)
@@ -2172,6 +2240,11 @@
21722240
_currentZoneID?.ownerName
21732241
}
21742242

2243+
private struct ActivityCounts {
2244+
var sendingChangesCount = 0
2245+
var fetchingChangesCount = 0
2246+
}
2247+
21752248
#if DEBUG
21762249
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
21772250
private struct NextRecordZoneChangeBatchLoggingState {

0 commit comments

Comments
 (0)