Skip to content

Commit 782257a

Browse files
mbrandonwmluisbrown
authored andcommitted
Show how animation can be done from effect. (#198)
* Show how animation can be done from effect. * clena up
1 parent af4c806 commit 782257a

File tree

4 files changed

+107
-8
lines changed

4 files changed

+107
-8
lines changed

Examples/CaseStudies/CaseStudies.xcodeproj/project.pbxproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
CA0C51FB245389CC00A04EAB /* 04-HigherOrderReducers-ResuableOfflineDownloadsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA0C51FA245389CC00A04EAB /* 04-HigherOrderReducers-ResuableOfflineDownloadsTests.swift */; };
1111
CA25E5D224463AD700DA666A /* 01-GettingStarted-Bindings-Basics.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA25E5D124463AD700DA666A /* 01-GettingStarted-Bindings-Basics.swift */; };
1212
CA27C0B7245780CE00CB1E59 /* 03-Effects-SystemEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA27C0B6245780CE00CB1E59 /* 03-Effects-SystemEnvironment.swift */; };
13+
CA34170824A4E89500FAF950 /* 01-GettingStarted-AnimationsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA34170724A4E89500FAF950 /* 01-GettingStarted-AnimationsTests.swift */; };
1314
CA410EE0247A15FE00E41798 /* 02-Effects-WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA410EDF247A15FE00E41798 /* 02-Effects-WebSocket.swift */; };
1415
CA410EE2247C73B400E41798 /* 02-Effects-WebSocketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA410EE1247C73B400E41798 /* 02-Effects-WebSocketTests.swift */; };
1516
CA6AC2642451135C00C71CB3 /* ReusableComponents-Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA6AC2602451135C00C71CB3 /* ReusableComponents-Download.swift */; };
@@ -127,6 +128,7 @@
127128
CA0C51FA245389CC00A04EAB /* 04-HigherOrderReducers-ResuableOfflineDownloadsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "04-HigherOrderReducers-ResuableOfflineDownloadsTests.swift"; sourceTree = "<group>"; };
128129
CA25E5D124463AD700DA666A /* 01-GettingStarted-Bindings-Basics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "01-GettingStarted-Bindings-Basics.swift"; sourceTree = "<group>"; };
129130
CA27C0B6245780CE00CB1E59 /* 03-Effects-SystemEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "03-Effects-SystemEnvironment.swift"; sourceTree = "<group>"; };
131+
CA34170724A4E89500FAF950 /* 01-GettingStarted-AnimationsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "01-GettingStarted-AnimationsTests.swift"; sourceTree = "<group>"; };
130132
CA410EDF247A15FE00E41798 /* 02-Effects-WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "02-Effects-WebSocket.swift"; sourceTree = "<group>"; };
131133
CA410EE1247C73B400E41798 /* 02-Effects-WebSocketTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "02-Effects-WebSocketTests.swift"; sourceTree = "<group>"; };
132134
CA6AC2602451135C00C71CB3 /* ReusableComponents-Download.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ReusableComponents-Download.swift"; sourceTree = "<group>"; };
@@ -339,13 +341,14 @@
339341
DC89C42C24460F96006900B9 /* SwiftUICaseStudiesTests */ = {
340342
isa = PBXGroup;
341343
children = (
344+
CA34170724A4E89500FAF950 /* 01-GettingStarted-AnimationsTests.swift */,
345+
CAA9ADC324465AB00003A984 /* 02-Effects-BasicsTests.swift */,
342346
CAA9ADC724465D950003A984 /* 02-Effects-CancellationTests.swift */,
343347
CAA9ADCB2446615B0003A984 /* 02-Effects-LongLivingTests.swift */,
344-
CAA9ADC324465AB00003A984 /* 02-Effects-BasicsTests.swift */,
345348
DC07231624465D1E003A8B65 /* 02-Effects-TimersTests.swift */,
346349
CA410EE1247C73B400E41798 /* 02-Effects-WebSocketTests.swift */,
347-
DC634B242448D15B00DAA016 /* 04-HigherOrderReducers-ReusableFavoritingTests.swift */,
348350
CA0C51FA245389CC00A04EAB /* 04-HigherOrderReducers-ResuableOfflineDownloadsTests.swift */,
351+
DC634B242448D15B00DAA016 /* 04-HigherOrderReducers-ReusableFavoritingTests.swift */,
349352
);
350353
path = SwiftUICaseStudiesTests;
351354
sourceTree = "<group>";
@@ -608,6 +611,7 @@
608611
DC634B252448D15B00DAA016 /* 04-HigherOrderReducers-ReusableFavoritingTests.swift in Sources */,
609612
CAA9ADC824465D950003A984 /* 02-Effects-CancellationTests.swift in Sources */,
610613
CA410EE2247C73B400E41798 /* 02-Effects-WebSocketTests.swift in Sources */,
614+
CA34170824A4E89500FAF950 /* 01-GettingStarted-AnimationsTests.swift in Sources */,
611615
DC07231724465D1E003A8B65 /* 02-Effects-TimersTests.swift in Sources */,
612616
CAA9ADC424465AB00003A984 /* 02-Effects-BasicsTests.swift in Sources */,
613617
CAA9ADCC2446615B0003A984 /* 02-Effects-LongLivingTests.swift in Sources */,

Examples/CaseStudies/SwiftUICaseStudies/00-RootView.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ struct RootView: View {
6969
store: Store(
7070
initialState: AnimationsState(circleCenter: CGPoint(x: 50, y: 50)),
7171
reducer: animationsReducer,
72-
environment: AnimationsEnvironment()
72+
environment: AnimationsEnvironment(
73+
mainQueue: DispatchQueue.main.eraseToAnyScheduler()
74+
)
7375
)
7476
)
7577
)

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

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,20 @@ private let readMe = """
1818

1919
struct AnimationsState: Equatable {
2020
var circleCenter = CGPoint.zero
21+
var circleColor = Color.white
2122
var isCircleScaled = false
2223
}
2324

2425
enum AnimationsAction: Equatable {
2526
case circleScaleToggleChanged(Bool)
27+
case rainbowButtonTapped
28+
case setColor(Color)
2629
case tapped(CGPoint)
2730
}
2831

29-
struct AnimationsEnvironment {}
32+
struct AnimationsEnvironment {
33+
var mainQueue: AnySchedulerOf<DispatchQueue>
34+
}
3035

3136
let animationsReducer = Reducer<AnimationsState, AnimationsAction, AnimationsEnvironment> {
3237
state, action, environment in
@@ -36,6 +41,23 @@ let animationsReducer = Reducer<AnimationsState, AnimationsAction, AnimationsEnv
3641
state.isCircleScaled = isScaled
3742
return .none
3843

44+
case .rainbowButtonTapped:
45+
return .concatenate(
46+
[Color.red, .blue, .green, .orange, .pink, .purple, .yellow, .white]
47+
.enumerated()
48+
.map { index, color in
49+
index == 0
50+
? Effect(value: .setColor(color))
51+
: Effect(value: .setColor(color))
52+
.delay(for: 1, scheduler: environment.mainQueue)
53+
.eraseToEffect()
54+
}
55+
)
56+
57+
case let .setColor(color):
58+
state.circleColor = color
59+
return .none
60+
3961
case let .tapped(point):
4062
state.circleCenter = point
4163
return .none
@@ -49,13 +71,14 @@ struct AnimationsView: View {
4971
var body: some View {
5072
GeometryReader { proxy in
5173
WithViewStore(self.store) { viewStore in
52-
VStack {
74+
VStack(alignment: .leading) {
5375
ZStack(alignment: .center) {
5476
Text(template: readMe, .body)
5577
.padding()
5678

5779
Circle()
58-
.fill(Color.white)
80+
.fill(viewStore.circleColor)
81+
.animation(.linear)
5982
.blendMode(.difference)
6083
.frame(width: 50, height: 50)
6184
.scaleEffect(viewStore.isCircleScaled ? 2 : 1)
@@ -81,6 +104,8 @@ struct AnimationsView: View {
81104
.animation(.interactiveSpring(response: 0.25, dampingFraction: 0.1))
82105
)
83106
.padding()
107+
Button("Rainbow") { viewStore.send(.rainbowButtonTapped) }
108+
.padding([.leading, .trailing, .bottom])
84109
}
85110
}
86111
}
@@ -95,7 +120,9 @@ struct AnimationsView_Previews: PreviewProvider {
95120
store: Store(
96121
initialState: AnimationsState(circleCenter: CGPoint(x: 50, y: 50)),
97122
reducer: animationsReducer,
98-
environment: AnimationsEnvironment()
123+
environment: AnimationsEnvironment(
124+
mainQueue: DispatchQueue.main.eraseToAnyScheduler()
125+
)
99126
)
100127
)
101128
}
@@ -105,7 +132,9 @@ struct AnimationsView_Previews: PreviewProvider {
105132
store: Store(
106133
initialState: AnimationsState(circleCenter: CGPoint(x: 50, y: 50)),
107134
reducer: animationsReducer,
108-
environment: AnimationsEnvironment()
135+
environment: AnimationsEnvironment(
136+
mainQueue: DispatchQueue.main.eraseToAnyScheduler()
137+
)
109138
)
110139
)
111140
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import Combine
2+
import ComposableArchitecture
3+
import XCTest
4+
5+
@testable import SwiftUICaseStudies
6+
7+
class AnimationTests: XCTestCase {
8+
let scheduler = DispatchQueue.testScheduler
9+
10+
func testRainbow() {
11+
let store = TestStore(
12+
initialState: AnimationsState(),
13+
reducer: animationsReducer,
14+
environment: AnimationsEnvironment(
15+
mainQueue: self.scheduler.eraseToAnyScheduler()
16+
)
17+
)
18+
19+
store.assert(
20+
.send(.rainbowButtonTapped),
21+
22+
.receive(.setColor(.red)) {
23+
$0.circleColor = .red
24+
},
25+
26+
.do { self.scheduler.advance(by: .seconds(1)) },
27+
.receive(.setColor(.blue)) {
28+
$0.circleColor = .blue
29+
},
30+
31+
.do { self.scheduler.advance(by: .seconds(1)) },
32+
.receive(.setColor(.green)) {
33+
$0.circleColor = .green
34+
},
35+
36+
.do { self.scheduler.advance(by: .seconds(1)) },
37+
.receive(.setColor(.orange)) {
38+
$0.circleColor = .orange
39+
},
40+
41+
.do { self.scheduler.advance(by: .seconds(1)) },
42+
.receive(.setColor(.pink)) {
43+
$0.circleColor = .pink
44+
},
45+
46+
.do { self.scheduler.advance(by: .seconds(1)) },
47+
.receive(.setColor(.purple)) {
48+
$0.circleColor = .purple
49+
},
50+
51+
.do { self.scheduler.advance(by: .seconds(1)) },
52+
.receive(.setColor(.yellow)) {
53+
$0.circleColor = .yellow
54+
},
55+
56+
.do { self.scheduler.advance(by: .seconds(1)) },
57+
.receive(.setColor(.white)) {
58+
$0.circleColor = .white
59+
},
60+
61+
.do { self.scheduler.run() }
62+
)
63+
}
64+
}

0 commit comments

Comments
 (0)