Skip to content

Instantly share code, notes, and snippets.

@kielgillard
Created March 3, 2021 05:24
Show Gist options
  • Save kielgillard/6a8732cf11afc75d94447623daf99da6 to your computer and use it in GitHub Desktop.
Save kielgillard/6a8732cf11afc75d94447623daf99da6 to your computer and use it in GitHub Desktop.
0136-swiftui-animation-pt2 + env schedulers
/**
diff --git a/0136-swiftui-animation-pt2/Animations/Animations/AnimationsApp.swift b/0136-swiftui-animation-pt2/Animations/Animations/AnimationsApp.swift
index a649d86..23058ba 100644
--- a/0136-swiftui-animation-pt2/Animations/Animations/AnimationsApp.swift
+++ b/0136-swiftui-animation-pt2/Animations/Animations/AnimationsApp.swift
@@ -5,7 +5,12 @@ struct AnimationsApp: App {
var body: some Scene {
WindowGroup {
// ContentView()
- TCAContentView(store: .init(initialState: .init(), reducer: appReducer, environment: .init()))
+ TCAContentView(store: .init(initialState: .init(),
+ reducer: appReducer,
+ environment: .init(
+ mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
+ oddNumberQueue: DispatchQueue.main.oddNumberAnimation(),
+ evenNumberQueue: DispatchQueue.main.evenNumberAnimation())))
}
}
}
diff --git a/0136-swiftui-animation-pt2/Animations/Animations/AnimationsTCA.swift b/0136-swiftui-animation-pt2/Animations/Animations/AnimationsTCA.swift
index 70d5f2c..7c06322 100644
--- a/0136-swiftui-animation-pt2/Animations/Animations/AnimationsTCA.swift
+++ b/0136-swiftui-animation-pt2/Animations/Animations/AnimationsTCA.swift
@@ -15,11 +15,24 @@ enum AppAction {
case toggleScale(isOn: Bool)
}
-struct AppEnvironment {}
+struct AppEnvironment {
+ var mainQueue: AnySchedulerOf<DispatchQueue>
+ var oddNumberQueue: AnySchedulerOf<DispatchQueue>
+ var evenNumberQueue: AnySchedulerOf<DispatchQueue>
+}
import Combine
extension Scheduler {
+
+ func oddNumberAnimation() -> AnySchedulerOf<Self> {
+ animation(.none)
+ }
+
+ func evenNumberAnimation() -> AnySchedulerOf<Self> {
+ animation(.linear)
+ }
+
func animation(_ animation: Animation? = .default) -> AnySchedulerOf<Self> {
.init(
minimumTolerance: { self.minimumTolerance },
@@ -66,7 +79,7 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment> {
// withAnimation: (() -> R) -> R
[Color.blue, .green, .purple, .black].enumerated().map { index, color in
Effect(value: withAnimation { .setCircleColor(color) })
- .delay(for: .seconds(1), scheduler: DispatchQueue.main.animation(index.isMultiple(of: 2) ? nil : .linear))
+ .delay(for: .seconds(1), scheduler: index.isMultiple(of: 2) ? environment.evenNumberQueue : environment.oddNumberQueue)
.eraseToEffect()
}
// Effect(value: AppAction.setCircleColor(.blue))
@@ -221,7 +234,10 @@ struct TCAContentView_Previews: PreviewProvider {
store: Store(
initialState: AppState(),
reducer: appReducer,
- environment: AppEnvironment()
+ environment: AppEnvironment(
+ mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
+ oddNumberQueue: DispatchQueue.main.oddNumberAnimation(),
+ evenNumberQueue: DispatchQueue.main.evenNumberAnimation())
)
)
}
*/
import ComposableArchitecture
import SwiftUI
struct AppState: Equatable {
var circleCenter = CGPoint.zero
var circleColor = Color.black
var isCircleScaled = false
}
enum AppAction {
case cycleColorsButtonTapped
case dragGesture(CGPoint)
case resetButtonTapped
case setCircleColor(Color)
case toggleScale(isOn: Bool)
}
struct AppEnvironment {
var mainQueue: AnySchedulerOf<DispatchQueue>
var oddNumberQueue: AnySchedulerOf<DispatchQueue>
var evenNumberQueue: AnySchedulerOf<DispatchQueue>
}
import Combine
extension Scheduler {
func oddNumberAnimation() -> AnySchedulerOf<Self> {
animation(.none)
}
func evenNumberAnimation() -> AnySchedulerOf<Self> {
animation(.linear)
}
func animation(_ animation: Animation? = .default) -> AnySchedulerOf<Self> {
.init(
minimumTolerance: { self.minimumTolerance },
now: { self.now },
scheduleImmediately: { options, action in
self.schedule(options: options) {
withAnimation(animation, action)
}
},
delayed: { after, tolerance, options, action in
self.schedule(after: after, tolerance: tolerance, options: options) {
withAnimation(animation, action)
}
},
interval: { after, interval, tolerance, options, action in
self.schedule(after: after, interval: interval, tolerance: tolerance, options: options) {
withAnimation(animation, action)
}
}
)
}
}
//let mainQueue = AnyScheduler(
// minimumTolerance: { DispatchQueue.main.minimumTolerance },
// now: { DispatchQueue.main.now },
// scheduleImmediately: DispatchQueue.main.schedule,
// delayed: { duration, tolerance, options, action in
// DispatchQueue.main.schedule(after: duration, tolerance: tolerance, options: options) {
// withAnimation {
// action()
// }
// }
// },
// interval: DispatchQueue.main.schedule
//)
let appReducer = Reducer<AppState, AppAction, AppEnvironment> {
state, action, environment in
switch action {
case .cycleColorsButtonTapped:
state.circleColor = .red
return Effect.concatenate(
// withAnimation: (() -> R) -> R
[Color.blue, .green, .purple, .black].enumerated().map { index, color in
Effect(value: withAnimation { .setCircleColor(color) })
.delay(for: .seconds(1), scheduler: index.isMultiple(of: 2) ? environment.evenNumberQueue : environment.oddNumberQueue)
.eraseToEffect()
}
// Effect(value: AppAction.setCircleColor(.blue))
// .delay(for: .seconds(1), scheduler: DispatchQueue.main)
// .eraseToEffect(),
// Effect(value: AppAction.setCircleColor(.green))
// .delay(for: .seconds(1), scheduler: DispatchQueue.main)
// .eraseToEffect(),
// Effect(value: AppAction.setCircleColor(.purple))
// .delay(for: .seconds(1), scheduler: DispatchQueue.main)
// .eraseToEffect(),
// Effect(value: AppAction.setCircleColor(.black))
// .delay(for: .seconds(1), scheduler: DispatchQueue.main)
// .eraseToEffect()
)
case let .dragGesture(location):
state.circleCenter = location
return .none
case .resetButtonTapped:
state = AppState()
return .none
case let .setCircleColor(color):
state.circleColor = color
return .none
case .toggleScale(isOn: let isOn):
state.isCircleScaled = isOn
return .none
}
}
extension ViewStore {
func send(_ action: Action, animation: Animation) {
withAnimation(animation) {
self.send(action)
}
}
}
struct TCAContentView: View {
let store: Store<AppState, AppAction>
// @State var circleCenter = CGPoint.zero
// @State var circleColor = Color.black
// @State var isCircleScaled = false
// @State var isResetting = false
var body: some View {
WithViewStore(self.store) { viewStore in
VStack {
Circle()
.fill(viewStore.circleColor)
// .animation(.linear)
.overlay(Text("Hi"))
.frame(width: 50, height: 50)
.scaleEffect(viewStore.isCircleScaled ? 2 : 1)
// .animation(nil, value: self.isCircleScaled)
// .animation(.disabled)
.offset(x: viewStore.circleCenter.x - 25, y: viewStore.circleCenter.y - 25)
// .animation(.spring(response: 0.3, dampingFraction: 0.1))
.gesture(
DragGesture(minimumDistance: 0).onChanged { value in
// withAnimation(.spring(response: 0.3, dampingFraction: 0.1)) {
//// viewStore.circleCenter = value.location
// viewStore.send(.dragGesture(value.location))
// }
viewStore.send(.dragGesture(value.location), animation: .spring(response: 0.3, dampingFraction: 0.1))
}
)
.foregroundColor(viewStore.isCircleScaled ? .red : nil)
// if self.isCircleScaled {
// circle.foregroundColor(.red)
// } else {
// circle
// }
Toggle(
"Scale",
isOn: viewStore.binding(
get: \.isCircleScaled,
send: AppAction.toggleScale(isOn:)
)
.animation(.spring(response: 0.3, dampingFraction: 0.1))
// self.$isCircleScaled.animation(.spring(response: 0.3, dampingFraction: 0.1))
// Binding(
// get: { self.isCircleScaled },
// set: { isOn in
// withAnimation(.spring(response: 0.3, dampingFraction: 0.1)) {
// self.isCircleScaled = isOn
// }
// }
// )
)
Button("Cycle colors") {
withAnimation(.linear) {
viewStore.send(.cycleColorsButtonTapped)
}
// [Color.red, .blue, .green, .purple, .black]
// .enumerated()
// .forEach { offset, color in
// DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(offset)) {
// withAnimation(.linear) {
// self.circleColor = color
// }
//
// // withAnimation: (() -> R) -> R
// // Async<A> = ((A) -> Void) -> Void
// // ((A) -> R) -> R
// }
// }
// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// self.circleColor = .blue
// }
// DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
// self.circleColor = .green
// }
// DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
// self.circleColor = .purple
// }
// DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
// self.circleColor = .black
// }
}
Button("Reset") {
// withAnimation {
viewStore.send(.resetButtonTapped, animation: .linear)
// }
// self.isResetting = true
// withAnimation {
// self.circleCenter = .zero
// self.circleColor = .black
// }
// self.isCircleScaled = false
// DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1)) {
// self.isResetting = false
// }
}
}
}
}
}
struct TCAContentView_Previews: PreviewProvider {
static var previews: some View {
TCAContentView(
store: Store(
initialState: AppState(),
reducer: appReducer,
environment: AppEnvironment(
mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
oddNumberQueue: DispatchQueue.main.oddNumberAnimation(),
evenNumberQueue: DispatchQueue.main.evenNumberAnimation())
)
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment