diff --git a/Sources/Atoms/AtomRoot.swift b/Sources/Atoms/AtomRoot.swift index ee359b1e..eca761c4 100644 --- a/Sources/Atoms/AtomRoot.swift +++ b/Sources/Atoms/AtomRoot.swift @@ -42,9 +42,22 @@ import SwiftUI /// } /// ``` /// +/// Additionally, if for some reason you want to manage the store on your own, +/// you can pass the instance to allow descendant views to store atom values +/// in the given store. +/// +/// ```swift +/// let store = AtomStore() +/// let rootView = AtomRoot(storesIn: store) { +/// RootView() +/// } +/// let window = UIWindow(frame: UIScreen.main.bounds) +/// window.rootViewController = UIHostingController(rootView: rootView) +/// window.makeKeyAndVisible() +/// ``` +/// public struct AtomRoot: View { - @StateObject - private var state = State() + private var storeHost: StoreHost private var overrides = [OverrideKey: any AtomOverrideProtocol]() private var observers = [Observer]() private let content: Content @@ -53,20 +66,42 @@ public struct AtomRoot: View { /// /// - Parameter content: The content that uses atoms. public init(@ViewBuilder content: () -> Content) { + self.storeHost = .tree + self.content = content() + } + + /// Creates a new scope with the specified content that will be allowed to use atoms by + /// passing a store object. + /// + /// - Parameters: + /// - store: An object that stores the state of atoms. + /// - content: The view content that inheriting from the parent. + public init( + storesIn store: AtomStore, + @ViewBuilder content: () -> Content + ) { + self.storeHost = .unmanaged(store: store) self.content = content() } /// The content and behavior of the view. public var body: some View { - content.environment( - \.store, - StoreContext( - state.store, - scopeKey: ScopeKey(token: state.token), - observers: observers, - overrides: overrides + switch storeHost { + case .tree: + TreeHostedStore( + content: content, + overrides: overrides, + observers: observers + ) + + case .unmanaged(let store): + UnmanagedStore( + content: content, + store: store, + overrides: overrides, + observers: observers ) - ) + } } /// For debugging purposes, each time there is a change in the internal state, @@ -112,10 +147,63 @@ public struct AtomRoot: View { } private extension AtomRoot { - @MainActor - final class State: ObservableObject { - let store = AtomStore() - let token = ScopeKey.Token() + enum StoreHost { + case tree + case unmanaged(store: AtomStore) + } + + struct TreeHostedStore: View { + @MainActor + final class State: ObservableObject { + let store = AtomStore() + let token = ScopeKey.Token() + } + + let content: Content + let overrides: [OverrideKey: any AtomOverrideProtocol] + let observers: [Observer] + + @StateObject + private var state = State() + + var body: some View { + content.environment( + \.store, + StoreContext( + state.store, + scopeKey: ScopeKey(token: state.token), + observers: observers, + overrides: overrides + ) + ) + } + } + + struct UnmanagedStore: View { + @MainActor + final class State: ObservableObject { + let token = ScopeKey.Token() + } + + let content: Content + let store: AtomStore + let overrides: [OverrideKey: any AtomOverrideProtocol] + let observers: [Observer] + + @StateObject + private var state = State() + + var body: some View { + content.environment( + \.store, + StoreContext( + store, + scopeKey: ScopeKey(token: state.token), + observers: observers, + overrides: overrides + ) + ) + } } func `mutating`(_ mutation: (inout Self) -> Void) -> Self { diff --git a/Sources/Atoms/AtomScope.swift b/Sources/Atoms/AtomScope.swift index 50f858ab..aa3d1dc4 100644 --- a/Sources/Atoms/AtomScope.swift +++ b/Sources/Atoms/AtomScope.swift @@ -35,20 +35,6 @@ import SwiftUI /// } /// ``` /// -/// Additionally, if for some reason your app cannot use ``AtomRoot`` to manage the store, -/// you can instead manage the store on your own and pass the instance to ``AtomScope`` -/// to allow descendant views to store atom values in the given store. -/// -/// ```swift -/// let store = AtomStore() -/// let rootView = AtomScope(storesIn: store) { -/// RootView() -/// } -/// let window = UIWindow(frame: UIScreen.main.bounds) -/// window.rootViewController = UIHostingController(rootView: rootView) -/// window.makeKeyAndVisible() -/// ``` -/// public struct AtomScope: View { private let inheritance: Inheritance private var overrides = [OverrideKey: any AtomOverrideProtocol]() @@ -77,42 +63,20 @@ public struct AtomScope: View { self.content = content() } - /// Creates a new scope with the specified content that will be allowed to use atoms by - /// passing a store object. - /// - /// - Parameters: - /// - store: An object that stores the state of atoms. - /// - content: The view content that inheriting from the parent. - public init( - storesIn store: AtomStore, - @ViewBuilder content: () -> Content - ) { - self.inheritance = .store(store) - self.content = content() - } - /// The content and behavior of the view. public var body: some View { switch inheritance { - case .context(let context): - InheritedContext( - content: content, - context: context, - overrides: overrides, - observers: observers - ) - - case .store(let store): - InheritedStore( + case .environment: + InheritedEnvironment( content: content, - store: store, overrides: overrides, observers: observers ) - case .environment: - InheritedEnvironment( + case .context(let context): + InheritedContext( content: content, + context: context, overrides: overrides, observers: observers ) @@ -170,42 +134,29 @@ public struct AtomScope: View { private extension AtomScope { enum Inheritance { - case context(AtomViewContext) - case store(AtomStore) case environment + case context(AtomViewContext) } - struct InheritedContext: View { - let content: Content - let context: AtomViewContext - let overrides: [OverrideKey: any AtomOverrideProtocol] - let observers: [Observer] - - var body: some View { - content.environment( - \.store, - context._store.inherited( - observers: observers, - overrides: overrides - ) - ) + struct InheritedEnvironment: View { + @MainActor + final class State: ObservableObject { + let token = ScopeKey.Token() } - } - struct InheritedStore: View { let content: Content - let store: AtomStore let overrides: [OverrideKey: any AtomOverrideProtocol] let observers: [Observer] @StateObject - private var state = ScopeState() + private var state = State() + @Environment(\.store) + private var environmentStore var body: some View { content.environment( \.store, - StoreContext( - store, + environmentStore.scoped( scopeKey: ScopeKey(token: state.token), observers: observers, overrides: overrides @@ -214,21 +165,16 @@ private extension AtomScope { } } - struct InheritedEnvironment: View { + struct InheritedContext: View { let content: Content + let context: AtomViewContext let overrides: [OverrideKey: any AtomOverrideProtocol] let observers: [Observer] - @StateObject - private var state = ScopeState() - @Environment(\.store) - private var environmentStore - var body: some View { content.environment( \.store, - environmentStore.scoped( - scopeKey: ScopeKey(token: state.token), + context._store.inherited( observers: observers, overrides: overrides ) @@ -236,11 +182,6 @@ private extension AtomScope { } } - @MainActor - final class ScopeState: ObservableObject { - let token = ScopeKey.Token() - } - func `mutating`(_ mutation: (inout Self) -> Void) -> Self { var view = self mutation(&view) diff --git a/Sources/Atoms/Deprecated.swift b/Sources/Atoms/Deprecated.swift index 15e3fdcb..89b0826d 100644 --- a/Sources/Atoms/Deprecated.swift +++ b/Sources/Atoms/Deprecated.swift @@ -18,11 +18,13 @@ public extension AtomScope { self.init(inheriting: context, content: content) } - @available(*, deprecated, renamed: "init(storesIn:content:)") - init( + @available(*, deprecated, renamed: "AtomRoot.init(storesIn:content:)") + init( _ store: AtomStore, - @ViewBuilder content: () -> Content - ) { - self.init(storesIn: store, content: content) + @ViewBuilder content: () -> Root + ) where Content == AtomRoot { + self.init { + AtomRoot(storesIn: store, content: content) + } } }