Skip to content

Commit e3175b9

Browse files
authored
Add AtomDerivedScope and deprecate AtomScope(inheriting:content:) (#175)
* Add AtomDerivedScope * Description improvements * Refactoring
1 parent 9dd704c commit e3175b9

File tree

5 files changed

+81
-59
lines changed

5 files changed

+81
-59
lines changed

README.md

+19-16
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,23 @@ AtomScope {
12961296
}
12971297
```
12981298

1299+
#### [AtomDerivedScope](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomderivedscope)
1300+
1301+
`AtomDerivedScope` is useful when, for some reason, the atom store is not propagated through a view hierarchy. It explicitly propagate the atom store from the parent view via the given view context.
1302+
1303+
```swift
1304+
struct MailView: View {
1305+
@ViewContext
1306+
var context
1307+
1308+
var body: some View {
1309+
AtomDerivedScope(context) {
1310+
WrappedMailView()
1311+
}
1312+
}
1313+
}
1314+
```
1315+
12991316
#### [Suspense](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/suspense)
13001317

13011318
`Suspense` awaits the resulting value of the given `Task` and displays the content depending on its phase.
@@ -1476,20 +1493,6 @@ AtomScope {
14761493
}
14771494
```
14781495

1479-
If you want to inherit the overridden atom from the parent scope, you can explicitly pass `@ViewContext` context that has gotten in the parent scope. Then, the new scope completely inherits the parent scope's context.
1480-
1481-
```swift
1482-
@ViewContext
1483-
var context
1484-
1485-
var body: some {
1486-
// Inherites the parent scope's overrides.
1487-
AtomScope(inheriting: context) {
1488-
CountDisplay()
1489-
}
1490-
}
1491-
```
1492-
14931496
Note that overridden atoms in `AtomScope` automatically be scoped, but other atoms that depend on them will be in a shared state and must be given `Scoped` attribute (See also: [Scoped Atom](#scoped-atom)) in order to avoid it from being shared across out of scope.
14941497

14951498
See [Testing](#testing) section for details on dependency injection on unit tests.
@@ -1803,7 +1806,7 @@ class MessageLoader: ObservableObject {
18031806
#### Modal presentation causes assertionFailure when dismissing it (Fixed in iOS15)
18041807

18051808
Unfortunately, SwiftUI has a bug in iOS14 or lower where the `EnvironmentValue` is removed from a screen presented with `.sheet` just before dismissing it. Since this library is designed based on `EnvironmentValue`, this bug end up triggering the friendly `assertionFailure` that is added so that developers can easily aware of forgotten `AtomRoot` implementation.
1806-
As a workaround, `AtomScope` has the ability to explicitly inherit the store through `AtomViewContext` from the parent view.
1809+
As a workaround, you can use `AtomDerivedScope` to explicitly propagate the atom store via `AtomViewContext` from the parent view.
18071810

18081811
<details><summary><code>💡 Click to expand workaround</code></summary>
18091812

@@ -1820,7 +1823,7 @@ struct RootView: View {
18201823
Text("Example View")
18211824
}
18221825
.sheet(isPresented: $isPresented) {
1823-
AtomScope(inheriting: context) {
1826+
AtomDerivedScope(context) {
18241827
MailView()
18251828
}
18261829
}

Sources/Atoms/AtomDerivedScope.swift

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import SwiftUI
2+
3+
/// A view that derives the parent context.
4+
///
5+
/// Sometimes SwiftUI fails to propagate environment values in the view tree for some reason.
6+
/// This is a critical problem because the centralized state store of atoms is propagated through
7+
/// a view hierarchy via environment values.
8+
/// The typical example is that, in case you use SwiftUI view inside UIKit view, it could fail as
9+
/// SwiftUI can't pass environment values to UIKit across boundaries.
10+
/// In that case, you can wrap the view with ``AtomDerivedScope`` and pass a view context to it so that
11+
/// the descendant views can explicitly propagate the atom store.
12+
///
13+
/// ```swift
14+
/// @ViewContext
15+
/// var context
16+
///
17+
/// var body: some View {
18+
/// MyUIViewWrappingView {
19+
/// AtomDerivedScope(context) {
20+
/// MySwiftUIView()
21+
/// }
22+
/// }
23+
/// }
24+
/// ```
25+
///
26+
public struct AtomDerivedScope<Content: View>: View {
27+
private let store: StoreContext
28+
private let content: Content
29+
30+
/// Creates a derived scope with the specified content that will be allowed to use atoms by
31+
/// passing a view context to explicitly make the descendant views propagate the atom store.
32+
///
33+
/// - Parameters:
34+
/// - context: The parent view context that provides the atom store.
35+
/// - content: The descendant view content.
36+
public init(
37+
_ context: AtomViewContext,
38+
@ViewBuilder content: () -> Content
39+
) {
40+
self.store = context._store
41+
self.content = content()
42+
}
43+
44+
/// The content and behavior of the view.
45+
public var body: some View {
46+
content.environment(\.store, store)
47+
}
48+
}

Sources/Atoms/AtomScope.swift

+7-37
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,6 @@ import SwiftUI
2727
/// }
2828
/// ```
2929
///
30-
/// It inherits from the atom store provided by ``AtomRoot`` through environment values by default,
31-
/// but sometimes SwiftUI can fail to pass environment values in the view-tree for some reason.
32-
/// The typical example is that, in case you use SwiftUI view inside UIKit view, it could fail as
33-
/// SwiftUI can't pass environment values to UIKit across boundaries.
34-
/// In that case, you can wrap the view with ``AtomScope`` and pass a view context to it so that
35-
/// the descendant views can explicitly inherit the store.
36-
///
37-
/// ```swift
38-
/// @ViewContext
39-
/// var context
40-
///
41-
/// var body: some View {
42-
/// MyUIViewWrappingView {
43-
/// AtomScope(inheriting: context) {
44-
/// MySwiftUIView()
45-
/// }
46-
/// }
47-
/// }
48-
/// ```
49-
///
5030
public struct AtomScope<Content: View>: View {
5131
private let inheritance: Inheritance
5232
private var observers = [Observer]()
@@ -70,12 +50,12 @@ public struct AtomScope<Content: View>: View {
7050
/// - Parameters:
7151
/// - context: The parent view context that for inheriting store explicitly.
7252
/// - content: The descendant view content that provides scoped context for atoms.
53+
@available(*, deprecated, message: "Use `AtomDerivedScope` instead")
7354
public init(
7455
inheriting context: AtomViewContext,
7556
@ViewBuilder content: () -> Content
7657
) {
77-
let store = context._store
78-
self.inheritance = .context(store: store)
58+
self.inheritance = .context(context)
7959
self.content = content()
8060
}
8161

@@ -90,11 +70,10 @@ public struct AtomScope<Content: View>: View {
9070
content: content
9171
)
9272

93-
case .context(let store):
94-
WithContext(
95-
store: store,
96-
content: content
97-
)
73+
case .context(let context):
74+
AtomDerivedScope(context) {
75+
content
76+
}
9877
}
9978
}
10079

@@ -173,7 +152,7 @@ public struct AtomScope<Content: View>: View {
173152
private extension AtomScope {
174153
enum Inheritance {
175154
case environment(scopeID: ScopeID)
176-
case context(store: StoreContext)
155+
case context(AtomViewContext)
177156
}
178157

179158
struct WithEnvironment: View {
@@ -199,13 +178,4 @@ private extension AtomScope {
199178
)
200179
}
201180
}
202-
203-
struct WithContext: View {
204-
let store: StoreContext
205-
let content: Content
206-
207-
var body: some View {
208-
content.environment(\.store, store)
209-
}
210-
}
211181
}

Sources/Atoms/Atoms.docc/Atoms.md

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Building state by compositing atoms automatically optimizes rendering based on i
6060

6161
- ``AtomRoot``
6262
- ``AtomScope``
63+
- ``AtomDerivedScope``
6364
- ``Suspense``
6465

6566
### Values

Sources/Atoms/PropertyWrapper/ViewContext.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ private extension ViewContext {
9393
}
9494
```
9595
96-
If for some reason the view tree is formed that does not inherit from `EnvironmentValues`,
97-
consider using `AtomScope` to pass it.
96+
If for some reason the view tree does not propagate `EnvironmentValues`,
97+
consider using `AtomDerivedScope` to propagate it explicitly.
9898
That happens when using SwiftUI view wrapped with `UIHostingController`.
9999
100100
```
@@ -104,21 +104,21 @@ private extension ViewContext {
104104
105105
var body: some View {
106106
UIViewWrappingView {
107-
AtomScope(inheriting: context) {
107+
AtomDerivedScope(context) {
108108
WrappedView()
109109
}
110110
}
111111
}
112112
}
113113
```
114114
115-
The modal screen presented by the `.sheet` modifier or etc, inherits from the environment values,
115+
The modal screen presented by the `.sheet` modifier or etc, propagates the environment values,
116116
but only in iOS14, there is a bug where the environment values will be dismantled during it is
117-
dismissing. This also can be avoided by using `AtomScope` to explicitly inherit from it.
117+
dismissing. This also can be avoided by using `AtomDerivedScope` to explicitly propagate it.
118118
119119
```
120120
.sheet(isPresented: ...) {
121-
AtomScope(inheriting: context) {
121+
AtomDerivedScope(context) {
122122
ExampleView()
123123
}
124124
}

0 commit comments

Comments
 (0)