diff --git a/Sources/SwiftCrossUI/State/ObservableProperty.swift b/Sources/SwiftCrossUI/State/ObservableProperty.swift new file mode 100644 index 0000000000..bb30020e2e --- /dev/null +++ b/Sources/SwiftCrossUI/State/ObservableProperty.swift @@ -0,0 +1,11 @@ +import Foundation + +/// View properties that conform to ObservableProperty are automatically observed by SwiftCrossUI. +/// +/// This protocol is intended to be implemented by property wrappers. You shouldn't +/// have to implement it for your own model types. +public protocol ObservableProperty: DynamicProperty { + var didChange: Publisher { get } + func tryRestoreFromSnapshot(_ snapshot: Data) + func snapshot() throws -> Data? +} diff --git a/Sources/SwiftCrossUI/State/State.swift b/Sources/SwiftCrossUI/State/State.swift index 9b3219c68f..e49fc1e696 100644 --- a/Sources/SwiftCrossUI/State/State.swift +++ b/Sources/SwiftCrossUI/State/State.swift @@ -5,7 +5,7 @@ import Foundation // - It supports ObservableObject // - It supports Optional @propertyWrapper -public struct State: DynamicProperty, StateProperty { +public struct State: ObservableProperty { class Storage { // This inner box is what stays constant between view updates. The // outer box (Storage) is used so that we can assign this box to @@ -55,7 +55,7 @@ public struct State: DynamicProperty, StateProperty { var storage: Storage - var didChange: Publisher { + public var didChange: Publisher { storage.box.didChange } @@ -109,7 +109,7 @@ public struct State: DynamicProperty, StateProperty { } } - func tryRestoreFromSnapshot(_ snapshot: Data) { + public func tryRestoreFromSnapshot(_ snapshot: Data) { guard let decodable = Value.self as? Codable.Type, let state = try? JSONDecoder().decode(decodable, from: snapshot) @@ -120,7 +120,7 @@ public struct State: DynamicProperty, StateProperty { storage.box.value = state as! Value } - func snapshot() throws -> Data? { + public func snapshot() throws -> Data? { if let value = storage.box as? Codable { return try JSONEncoder().encode(value) } else { @@ -128,9 +128,3 @@ public struct State: DynamicProperty, StateProperty { } } } - -protocol StateProperty { - var didChange: Publisher { get } - func tryRestoreFromSnapshot(_ snapshot: Data) - func snapshot() throws -> Data? -} diff --git a/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift b/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift index b80b848699..b74ab602cb 100644 --- a/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift +++ b/Sources/SwiftCrossUI/ViewGraph/ViewGraphNode.swift @@ -116,7 +116,7 @@ public class ViewGraphNode: Sendable { ) } - guard let value = property.value as? StateProperty else { + guard let value = property.value as? any ObservableProperty else { continue } diff --git a/Sources/SwiftCrossUI/ViewGraph/ViewGraphSnapshotter.swift b/Sources/SwiftCrossUI/ViewGraph/ViewGraphSnapshotter.swift index ea7cd436e7..189bfb567c 100644 --- a/Sources/SwiftCrossUI/ViewGraph/ViewGraphSnapshotter.swift +++ b/Sources/SwiftCrossUI/ViewGraph/ViewGraphSnapshotter.swift @@ -55,7 +55,7 @@ public struct ViewGraphSnapshotter: ErasedViewGraphNodeTransformer { let mirror = Mirror(reflecting: view) for property in mirror.children { guard - let stateProperty = property as? StateProperty, + let stateProperty = property as? any ObservableProperty, let propertyName = property.label, let encodedState = state[propertyName] else { @@ -80,7 +80,7 @@ public struct ViewGraphSnapshotter: ErasedViewGraphNodeTransformer { for property in mirror.children { guard let propertyName = property.label, - let property = property as? StateProperty, + let property = property as? any ObservableProperty, let encodedState = try? property.snapshot() else { continue diff --git a/Sources/SwiftCrossUI/_App.swift b/Sources/SwiftCrossUI/_App.swift index 4d0a0a3624..fe5a2a012a 100644 --- a/Sources/SwiftCrossUI/_App.swift +++ b/Sources/SwiftCrossUI/_App.swift @@ -66,7 +66,7 @@ class _App { ) } - guard let value = property.value as? StateProperty else { + guard let value = property.value as? any ObservableProperty else { continue }