Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AtomStore can now be hosted only in AtomRoot #108

Merged
merged 1 commit into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}
}
Loading