Skip to content

Commit caebd33

Browse files
authored
Add ViewStore.send(_:animation:). (#392)
1 parent e22776f commit caebd33

File tree

7 files changed

+46
-25
lines changed

7 files changed

+46
-25
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
"""
@@ -65,7 +68,7 @@ let animationsReducer = Reducer<AnimationsState, AnimationsAction, AnimationsEnv
6568
return .keyFrames(
6669
values: [Color.red, .blue, .green, .orange, .pink, .purple, .yellow, .white]
6770
.map { (output: .setColor($0), duration: 1) },
68-
scheduler: environment.mainQueue
71+
scheduler: environment.mainQueue.animation(.linear)
6972
)
7073

7174
case let .setColor(color):
@@ -92,7 +95,6 @@ struct AnimationsView: View {
9295

9396
Circle()
9497
.fill(viewStore.circleColor)
95-
.animation(.linear)
9698
.blendMode(.difference)
9799
.frame(width: 50, height: 50)
98100
.scaleEffect(viewStore.isCircleScaled ? 2 : 1)
@@ -105,9 +107,10 @@ struct AnimationsView: View {
105107
.background(self.colorScheme == .dark ? Color.black : .white)
106108
.simultaneousGesture(
107109
DragGesture(minimumDistance: 0).onChanged { gesture in
108-
withAnimation(.interactiveSpring(response: 0.25, dampingFraction: 0.1)) {
109-
viewStore.send(.tapped(gesture.location))
110-
}
110+
viewStore.send(
111+
.tapped(gesture.location),
112+
animation: .interactiveSpring(response: 0.25, dampingFraction: 0.1)
113+
)
111114
}
112115
)
113116
Toggle(
@@ -118,7 +121,7 @@ struct AnimationsView: View {
118121
.animation(.interactiveSpring(response: 0.25, dampingFraction: 0.1))
119122
)
120123
.padding()
121-
Button("Rainbow") { viewStore.send(.rainbowButtonTapped) }
124+
Button("Rainbow") { viewStore.send(.rainbowButtonTapped, animation: .linear) }
122125
.padding([.leading, .trailing, .bottom])
123126
}
124127
}

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,13 @@ let timersReducer = Reducer<TimersState, TimersAction, TimersEnvironment> {
3838
case .toggleTimerButtonTapped:
3939
state.isTimerActive.toggle()
4040
return state.isTimerActive
41-
? Effect.timer(id: TimerId(), every: 1, tolerance: .zero, on: environment.mainQueue)
42-
.map { _ in TimersAction.timerTicked }
41+
? Effect.timer(
42+
id: TimerId(),
43+
every: 1,
44+
tolerance: .zero,
45+
on: environment.mainQueue.animation(.interpolatingSpring(stiffness: 3000, damping: 40))
46+
)
47+
.map { _ in TimersAction.timerTicked }
4348
: Effect.cancel(id: TimerId())
4449
}
4550
}
@@ -94,7 +99,6 @@ struct TimersView: View {
9499
}
95100
.stroke(Color.black, lineWidth: 3)
96101
.rotationEffect(.degrees(Double(self.viewStore.secondsElapsed) * 360 / 60))
97-
.animation(Animation.interpolatingSpring(stiffness: 3000, damping: 40))
98102
}
99103
}
100104
.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
@@ -65,7 +65,12 @@ let clockReducer = Reducer<ClockState, ClockAction, ClockEnvironment>.combine(
6565
return [
6666
TimerId():
6767
Effect
68-
.timer(id: TimerId(), every: 1, tolerance: .zero, on: environment.mainQueue)
68+
.timer(
69+
id: TimerId(),
70+
every: 1,
71+
tolerance: .zero,
72+
on: environment.mainQueue.animation(.interpolatingSpring(stiffness: 3000, damping: 40))
73+
)
6974
.map { _ in .timerTicked }
7075
]
7176
}
@@ -119,7 +124,6 @@ struct ClockView: View {
119124
}
120125
.stroke(Color.black, lineWidth: 3)
121126
.rotationEffect(.degrees(Double(self.viewStore.secondsElapsed) * 360 / 60))
122-
.animation(Animation.interpolatingSpring(stiffness: 3000, damping: 40))
123127
}
124128
}
125129
.frame(width: 280, height: 280)

Examples/Todos/Todos/Todos.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
7878
case .todo(id: _, action: .checkBoxToggled):
7979
struct TodoCompletionId: Hashable {}
8080
return Effect(value: .sortCompletedTodos)
81-
.debounce(id: TodoCompletionId(), for: 1, scheduler: environment.mainQueue)
81+
.debounce(id: TodoCompletionId(), for: 1, scheduler: environment.mainQueue.animation())
8282

8383
case .todo:
8484
return .none
@@ -103,7 +103,7 @@ struct AppView: View {
103103
WithViewStore(self.store.scope(state: { $0.filter }, action: AppAction.filterPicked)) {
104104
filterViewStore in
105105
Picker(
106-
"Filter", selection: filterViewStore.binding(send: { $0 })
106+
"Filter", selection: filterViewStore.binding(send: { $0 }).animation()
107107
) {
108108
ForEach(Filter.allCases, id: \.self) { filter in
109109
Text(filter.rawValue).tag(filter)
@@ -126,9 +126,11 @@ struct AppView: View {
126126
.navigationBarItems(
127127
trailing: HStack(spacing: 20) {
128128
EditButton()
129-
Button("Clear Completed") { viewStore.send(.clearCompletedButtonTapped) }
130-
.disabled(viewStore.isClearCompletedButtonDisabled)
131-
Button("Add Todo") { viewStore.send(.addTodoButtonTapped) }
129+
Button("Clear Completed") {
130+
viewStore.send(.clearCompletedButtonTapped, animation: .default)
131+
}
132+
.disabled(viewStore.isClearCompletedButtonDisabled)
133+
Button("Add Todo") { viewStore.send(.addTodoButtonTapped, animation: .default) }
132134
}
133135
)
134136
.environment(

Examples/VoiceMemos/VoiceMemos/VoiceMemos.swift

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

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

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ let package = Package(
1717
)
1818
],
1919
dependencies: [
20-
.package(url: "https://github.com/pointfreeco/combine-schedulers", from: "0.1.0"),
21-
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "0.1.1"),
20+
.package(url: "https://github.com/pointfreeco/combine-schedulers", from: "0.3.1"),
21+
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "0.1.3"),
2222
],
2323
targets: [
2424
.target(
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)