Skip to content

Commit b4779b3

Browse files
authored
Helpers for making stores stateless/actionless (#45)
1 parent 0738afa commit b4779b3

File tree

4 files changed

+40
-16
lines changed

4 files changed

+40
-16
lines changed

Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-Animations.swift

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,27 +33,29 @@ struct AnimationsView: View {
3333
let store: Store<AnimationsState, AnimationsAction>
3434

3535
var body: some View {
36-
GeometryReader { proxy in
37-
WithViewStore(self.store) { viewStore in
36+
WithViewStore(self.store.stateless) { actionViewStore in
37+
GeometryReader { proxy in
3838
ZStack(alignment: .center) {
3939
Text(template: readMe, .body)
4040
.padding()
4141

42-
Circle()
43-
.fill(Color.white)
44-
.blendMode(.difference)
45-
.frame(width: 50, height: 50)
46-
.offset(
47-
x: viewStore.circleCenter.x - proxy.size.width / 2,
48-
y: viewStore.circleCenter.y - proxy.size.height / 2
42+
WithViewStore(self.store.scope(state: \.circleCenter)) { circleCenterViewStore in
43+
Circle()
44+
.fill(Color.white)
45+
.blendMode(.difference)
46+
.frame(width: 50, height: 50)
47+
.offset(
48+
x: circleCenterViewStore.x - proxy.size.width / 2,
49+
y: circleCenterViewStore.y - proxy.size.height / 2
4950
)
51+
}
5052
}
5153
.frame(maxWidth: .infinity, maxHeight: .infinity)
5254
.background(Color.white)
5355
.gesture(
5456
DragGesture(minimumDistance: 0).onChanged { gesture in
5557
withAnimation(.interactiveSpring(response: 0.25, dampingFraction: 0.1)) {
56-
viewStore.send(.tapped(gesture.location))
58+
actionViewStore.send(.tapped(gesture.location))
5759
}
5860
}
5961
)

Sources/ComposableArchitecture/Store.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,15 @@ public final class Store<State, Action> {
179179
}
180180
}
181181

182+
public var stateless: Store<Void, Action> {
183+
self.scope(state: { _ in () })
184+
}
185+
186+
public var actionless: Store<State, Never> {
187+
func absurd<A>(_ never: Never) -> A {}
188+
return self.scope(state: { $0 }, action: absurd)
189+
}
190+
182191
private init(
183192
initialState: State,
184193
reducer: @escaping (inout State, Action) -> Effect<Action, Never>

Sources/ComposableArchitecture/SwiftUI/IfLetStore.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import SwiftUI
22

33
/// A view that safely unwraps a store of optional state in order to show one of two views.
4-
/// When the underlying state is non-`nil`, the `then` closure will be performed with a `Store`
5-
/// that holds onto non-optional state, and otherwise the `else` closure will be performed.
4+
///
5+
/// When the underlying state is non-`nil`, the `then` closure will be performed with a `Store` that
6+
/// holds onto non-optional state, and otherwise the `else` closure will be performed.
67
///
78
/// This is useful for deciding between two views to show depending on an optional piece of state:
89
///
@@ -59,11 +60,8 @@ where IfContent: View, ElseContent: View {
5960
if let state = viewStore.state {
6061
return
6162
ViewBuilder.buildEither(first: self.ifContent(self.store.scope(state: { $0 ?? state })))
62-
as _ConditionalContent<IfContent, ElseContent>
6363
} else {
64-
return
65-
ViewBuilder.buildEither(second: self.elseContent())
66-
as _ConditionalContent<IfContent, ElseContent>
64+
return ViewBuilder.buildEither(second: self.elseContent())
6765
}
6866
}
6967
}

Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ extension WithViewStore where State: Equatable {
7676
}
7777
}
7878

79+
extension WithViewStore where State == Void {
80+
/// Initializes a structure that transforms a store into an observable view store in order to
81+
/// compute views from equatable store state.
82+
///
83+
/// - Parameters:
84+
/// - store: A store of equatable state.
85+
/// - content: A function that can generate content from a view store.
86+
public init(
87+
_ store: Store<State, Action>,
88+
@ViewBuilder content: @escaping (ViewStore<State, Action>) -> Content
89+
) {
90+
self.init(store, removeDuplicates: ==, content: content)
91+
}
92+
}
93+
7994
extension WithViewStore: DynamicViewContent where State: Collection, Content: DynamicViewContent {
8095
public typealias Data = State
8196

0 commit comments

Comments
 (0)