Skip to content

Commit

Permalink
AtomStore can now be hosted only in AtomRoot
Browse files Browse the repository at this point in the history
  • Loading branch information
ra1028 committed Apr 16, 2024
1 parent ef3660c commit 6a7ba15
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 95 deletions.
120 changes: 106 additions & 14 deletions Sources/Atoms/AtomRoot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,26 @@ 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()
///
/// struct Application: App {
/// var body: some Scene {
/// WindowGroup {
/// AtomRoot(storesIn: store) {
/// RootView()
/// }
/// }
/// }
/// }
/// ```
///
public struct AtomRoot<Content: View>: View {
@StateObject
private var state = State()
private var storeHost: StoreHost
private var overrides = [OverrideKey: any AtomOverrideProtocol]()
private var observers = [Observer]()
private let content: Content
Expand All @@ -53,20 +70,42 @@ public struct AtomRoot<Content: View>: 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,
Expand Down Expand Up @@ -112,10 +151,63 @@ public struct AtomRoot<Content: View>: 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 {
Expand Down
93 changes: 17 additions & 76 deletions Sources/Atoms/AtomScope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Content: View>: View {
private let inheritance: Inheritance
private var overrides = [OverrideKey: any AtomOverrideProtocol]()
Expand Down Expand Up @@ -77,42 +63,20 @@ public struct AtomScope<Content: View>: 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
)
Expand Down Expand Up @@ -170,42 +134,29 @@ public struct AtomScope<Content: View>: 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
Expand All @@ -214,33 +165,23 @@ 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
)
)
}
}

@MainActor
final class ScopeState: ObservableObject {
let token = ScopeKey.Token()
}

func `mutating`(_ mutation: (inout Self) -> Void) -> Self {
var view = self
mutation(&view)
Expand Down
12 changes: 7 additions & 5 deletions Sources/Atoms/Deprecated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Root: View>(
_ store: AtomStore,
@ViewBuilder content: () -> Content
) {
self.init(storesIn: store, content: content)
@ViewBuilder content: () -> Root
) where Content == AtomRoot<Root> {
self.init {
AtomRoot(storesIn: store, content: content)
}
}
}

0 comments on commit 6a7ba15

Please sign in to comment.