Skip to content

Commit 819abba

Browse files
committed
Add ViewStore.send(_:animation:). (#392)
1 parent 3a4583b commit 819abba

File tree

7 files changed

+43
-29
lines changed

7 files changed

+43
-29
lines changed

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ private let readMe = """
1313
1414
To animate changes made to state through a binding, use the `.animation` method on `Binding`.
1515
16+
To animate asynchronous changes made to state via effects, use the `.animation` method provided \
17+
by the CombineSchedulers library to receive asynchronous actions in an animated fashion.
18+
1619
Try it out by tapping or dragging anywhere on the screen to move the dot, and by flipping the \
1720
toggle at the bottom of the screen.
1821
"""
@@ -64,7 +67,7 @@ let animationsReducer = Reducer<AnimationsState, AnimationsAction, AnimationsEnv
6467
return .keyFrames(
6568
values: [Color.red, .blue, .green, .orange, .pink, .purple, .yellow, .white]
6669
.map { (output: .setColor($0), duration: 1) },
67-
scheduler: environment.mainQueue
70+
scheduler: environment.mainQueue.animation(.linear)
6871
)
6972

7073
case let .setColor(color):
@@ -91,7 +94,6 @@ struct AnimationsView: View {
9194

9295
Circle()
9396
.fill(viewStore.circleColor)
94-
.animation(.linear)
9597
.blendMode(.difference)
9698
.frame(width: 50, height: 50)
9799
.scaleEffect(viewStore.isCircleScaled ? 2 : 1)
@@ -104,9 +106,10 @@ struct AnimationsView: View {
104106
.background(self.colorScheme == .dark ? Color.black : .white)
105107
.simultaneousGesture(
106108
DragGesture(minimumDistance: 0).onChanged { gesture in
107-
withAnimation(.interactiveSpring(response: 0.25, dampingFraction: 0.1)) {
108-
viewStore.send(.tapped(gesture.location))
109-
}
109+
viewStore.send(
110+
.tapped(gesture.location),
111+
animation: .interactiveSpring(response: 0.25, dampingFraction: 0.1)
112+
)
110113
}
111114
)
112115
Toggle(
@@ -117,7 +120,7 @@ struct AnimationsView: View {
117120
.animation(.interactiveSpring(response: 0.25, dampingFraction: 0.1))
118121
)
119122
.padding()
120-
Button("Rainbow") { viewStore.send(.rainbowButtonTapped) }
123+
Button("Rainbow") { viewStore.send(.rainbowButtonTapped, animation: .linear) }
121124
.padding([.leading, .trailing, .bottom])
122125
}
123126
}

Examples/CaseStudies/SwiftUICaseStudies/02-Effects-Timers.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ let timersReducer = Reducer<TimersState, TimersAction, TimersEnvironment> {
4040
state.isTimerActive.toggle()
4141
return state.isTimerActive
4242
? Effect.timer(
43-
id: TimerId(), every: .seconds(1), tolerance: .seconds(0), on: environment.mainQueue
43+
id: TimerId(),
44+
every: .seconds(1),
45+
tolerance: .seconds(0),
46+
on: environment.mainQueue.animation(.interpolatingSpring(stiffness: 3000, damping: 40))
4447
)
4548
.map { _ in TimersAction.timerTicked }
4649
: Effect.cancel(id: TimerId())
@@ -97,7 +100,6 @@ struct TimersView: View {
97100
}
98101
.stroke(Color.black, lineWidth: 3)
99102
.rotationEffect(.degrees(Double(self.viewStore.secondsElapsed) * 360 / 60))
100-
.animation(Animation.interpolatingSpring(stiffness: 3000, damping: 40))
101103
}
102104
}
103105
.frame(width: 280, height: 280)

Examples/CaseStudies/SwiftUICaseStudies/04-HigherOrderReducers-ElmLikeSubscriptions.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ let clockReducer = Reducer<ClockState, ClockAction, ClockEnvironment>.combine(
6666
return [
6767
TimerId():
6868
Effect
69-
.timer(id: TimerId(), every: .seconds(1), tolerance: .seconds(0), on: environment.mainQueue)
69+
.timer(
70+
id: TimerId(),
71+
every: .seconds(1),
72+
tolerance: .seconds(0),
73+
on: environment.mainQueue.animation(.interpolatingSpring(stiffness: 3000, damping: 40))
74+
)
7075
.map { _ in .timerTicked }
7176
]
7277
}
@@ -120,7 +125,6 @@ struct ClockView: View {
120125
}
121126
.stroke(Color.black, lineWidth: 3)
122127
.rotationEffect(.degrees(Double(self.viewStore.secondsElapsed) * 360 / 60))
123-
.animation(Animation.interpolatingSpring(stiffness: 3000, damping: 40))
124128
}
125129
}
126130
.frame(width: 280, height: 280)

Examples/Todos/Todos/Todos.swift

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,17 +73,12 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
7373
case .todo(id: _, action: .checkBoxToggled):
7474
struct TodoCompletionId: Hashable {}
7575
return Effect(value: .sortCompletedTodos)
76-
.debounce(id: TodoCompletionId(), interval: 1, scheduler: environment.mainQueue)
76+
.debounce(id: TodoCompletionId(), interval: 1, scheduler: environment.mainQueue.animation())
7777

7878
case .todo:
7979
return .none
8080
}
81-
},
82-
todoReducer.forEach(
83-
state: \.todos,
84-
action: /AppAction.todo(id:action:),
85-
environment: { _ in TodoEnvironment() }
86-
)
81+
}
8782
)
8883

8984
.debugActions(actionFormat: .labelsOnly)
@@ -103,7 +98,7 @@ struct AppView: View {
10398
WithViewStore(self.store.scope(state: { $0.filter }, action: AppAction.filterPicked)) {
10499
filterViewStore in
105100
Picker(
106-
"Filter", selection: filterViewStore.binding(send: { $0 })
101+
"Filter", selection: filterViewStore.binding(send: { $0 }).animation()
107102
) {
108103
ForEach(Filter.allCases, id: \.self) { filter in
109104
Text(filter.rawValue).tag(filter)
@@ -126,9 +121,11 @@ struct AppView: View {
126121
.navigationBarItems(
127122
trailing: HStack(spacing: 20) {
128123
EditButton()
129-
Button("Clear Completed") { viewStore.send(.clearCompletedButtonTapped) }
130-
.disabled(viewStore.isClearCompletedButtonDisabled)
131-
Button("Add Todo") { viewStore.send(.addTodoButtonTapped) }
124+
Button("Clear Completed") {
125+
viewStore.send(.clearCompletedButtonTapped, animation: .default)
126+
}
127+
.disabled(viewStore.isClearCompletedButtonDisabled)
128+
Button("Add Todo") { viewStore.send(.addTodoButtonTapped, animation: .default) }
132129
}
133130
)
134131
.environment(

Examples/VoiceMemos/VoiceMemos/VoiceMemos.swift

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -212,13 +212,7 @@ struct VoiceMemosView: View {
212212
.foregroundColor(Color(.label))
213213
.frame(width: 74, height: 74)
214214

215-
Button(
216-
action: {
217-
withAnimation(.spring()) {
218-
viewStore.send(.recordButtonTapped)
219-
}
220-
}
221-
) {
215+
Button(action: { viewStore.send(.recordButtonTapped, animation: .spring()) }) {
222216
RoundedRectangle(cornerRadius: viewStore.currentRecording != nil ? 4 : 35)
223217
.foregroundColor(Color(.systemRed))
224218
.padding(viewStore.currentRecording != nil ? 17 : 2)

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ let package = Package(
1717
)
1818
],
1919
dependencies: [
20-
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "0.1.1"),
20+
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "0.1.3"),
2121
.package(url: "https://github.com/ReactiveCocoa/ReactiveSwift", from: "6.4.0"),
2222
],
2323
targets: [
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import SwiftUI
2+
3+
extension ViewStore {
4+
/// Sends an action to the store with a given animation.
5+
///
6+
/// - Parameters:
7+
/// - action: An action.
8+
/// - animation: An animation.
9+
public func send(_ action: Action, animation: Animation?) {
10+
withAnimation(animation) {
11+
self.send(action)
12+
}
13+
}
14+
}

0 commit comments

Comments
 (0)