Created
September 25, 2019 11:33
-
-
Save chriseidhof/f1bfea3b26ed23c26f2b016a2d618ba4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ContentView.swift | |
// AnimationTimingCurve | |
// | |
// Created by Chris Eidhof on 25.09.19. | |
// Copyright © 2019 Chris Eidhof. All rights reserved. | |
// | |
import SwiftUI | |
struct RecordTimingCurve: GeometryEffect { | |
var onChange: (CGFloat) -> () = { _ in () } | |
var animatableData: CGFloat = 0 { | |
didSet { | |
onChange(animatableData) | |
} | |
} | |
func effectValue(size: CGSize) -> ProjectionTransform { | |
return .init() | |
} | |
} | |
import Combine | |
final class AnimationTrace: ObservableObject { | |
let objectWillChange = PassthroughSubject<(), Never>() | |
var data: [(time: CFTimeInterval, value: CGFloat)] = [] | |
var startTime: CFTimeInterval { | |
data.first?.time ?? 0 | |
} | |
var endTime: CFTimeInterval { | |
data.last?.time ?? 0 | |
} | |
func record(_ value: CGFloat) { | |
data.append((CACurrentMediaTime(), value)) | |
// if value == 1 { | |
DispatchQueue.main.async { | |
print("Data count: \(self.data.count)") | |
self.objectWillChange.send() | |
} | |
// } | |
} | |
func reset() { | |
data = [] | |
} | |
} | |
struct Trace: Shape { | |
var values: [(CGFloat, CGFloat)] // the second component should be in range 0...1 | |
func path(in rect: CGRect) -> Path { | |
guard let f = values.first, let l = values.last else { return Path() } | |
let xOffset = f.0 | |
let xMultiplier = l.0 - f.0 | |
return Path { p in | |
p.move(to: CGPoint(x: rect.minX, y: rect.maxY)) | |
for value in values { | |
let point = CGPoint(x: rect.minX + ((value.0 - xOffset) / xMultiplier) * rect.width, y: rect.maxY - CGFloat(value.1) * rect.height) | |
p.addLine(to: point) | |
} | |
} | |
} | |
} | |
let animations: [(String, Animation)] = [ | |
("default", .default), | |
("linear(duration: 1)", .linear(duration: 1)), | |
("interpolatingSpring(stiffnes: 5, damping: 3)", .interpolatingSpring(stiffness: 5, damping: 3)), | |
(".easeInOut(duration: 1)", .easeInOut(duration: 1)), | |
(".easeIn(duration: 1)", .easeIn(duration: 1)), | |
(".easeOut(duration: 1)", .easeOut(duration: 1)), | |
(".interactiveSpring(response: 3, dampingFraction: 2, blendDuration: 1)", .interactiveSpring(response: 3, dampingFraction: 2, blendDuration: 1)), | |
(".spring", .spring()), | |
(".default.repeatCount(3)", Animation.default.repeatCount(3)), | |
] | |
struct ContentView: View { | |
@ObservedObject var trace = AnimationTrace() | |
@State var animating: Bool = false | |
@State var selectedAnimationIndex: Int = 0 | |
@State var slowAnimations: Bool = false | |
var selectedAnimation: (String, Animation) { | |
return animations[selectedAnimationIndex] | |
} | |
var body: some View { | |
VStack { | |
RoundedRectangle(cornerRadius: 10) | |
.fill(Color.pink) | |
.frame(width: 50, height: 50) | |
.offset(x: animating ? 100 : -100) | |
.modifier(RecordTimingCurve(onChange: { | |
self.trace.record($0) | |
}, animatableData: animating ? 1 : 0)) | |
VStack { | |
Trace(values: trace.data.map { | |
(CGFloat($0), $1) | |
}) | |
.stroke(Color.red, style: .init(lineWidth: 2)) | |
.frame(height: 150) | |
.background(Rectangle().stroke(Color.gray, style: .init(lineWidth: 1))) | |
HStack { | |
Text("0") | |
Spacer() | |
Text("\(trace.endTime - trace.startTime)") | |
} | |
}.frame(width: 200) | |
Spacer() | |
Picker(selection: $selectedAnimationIndex, label: EmptyView(), content: { | |
ForEach(0..<animations.count) { | |
Text(animations[$0].0) | |
} | |
}) | |
Button(action: { | |
self.animating = false | |
self.trace.reset() | |
withAnimation(self.selectedAnimation.1.speed(self.slowAnimations ? 0.25 : 1), { | |
self.animating = true | |
}) | |
}, label: { Text("Animate") }) | |
Toggle(isOn: $slowAnimations, label: { Text("Slow Animations") }) | |
} | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is very useful, thanks for sharing 👍
I am playing around with
timingCurve
, and this is a great way to see the effect.