Skip to content

Instantly share code, notes, and snippets.

@Koshimizu-Takehito
Created July 22, 2023 15:43
Show Gist options
  • Save Koshimizu-Takehito/b6c87f4a30f7a58dddd35fcd0b73c16b to your computer and use it in GitHub Desktop.
Save Koshimizu-Takehito/b6c87f4a30f7a58dddd35fcd0b73c16b to your computer and use it in GitHub Desktop.
CustomAnimationをグラフで描画
import SwiftUI
struct AnimationGridView: View {
@State
private var id: UUID = .init()
var body: some View {
ZStack {
Color(.systemBackground)
VStack {
HStack {
AnimationItemView(title: "easeIn", animation: Animation.easeIn(duration:))
AnimationItemView(title: "easeOut", animation: Animation.easeOut(duration:))
}
HStack {
AnimationItemView(title: "easeInOut", animation: Animation.easeInOut(duration:))
AnimationItemView(title: "custom", animation: Animation.custom(duration:))
.foregroundStyle(.purple)
.font(.title2.bold())
}
}.id(id)
}
.task {
await start()
}
}
func start() async {
id = .init()
try? await Task.sleep(nanoseconds: 2_100_000_000)
Task { await start() }
}
}
extension Animation {
static func custom(duration: TimeInterval) -> Self {
let start = UnitPoint(x: 0.1, y: 0.7)
let end = UnitPoint(x: 0.9, y: 0.3)
let bezier = UnitCurve.bezier(startControlPoint: start, endControlPoint: end)
return .timingCurve(bezier, duration: duration)
}
}
private struct AnimationRatio: Hashable {
let value: Double
let time: TimeInterval
}
private struct AnimationRatioKey: PreferenceKey {
static var defaultValue: AnimationRatio?
static func reduce(value: inout AnimationRatio?, nextValue: () -> AnimationRatio?) {
value = nextValue()
}
}
private struct AnimationItemView: View {
@State
private var ratio: Double = 0
@State
private var history: [AnimationRatio] = []
var title: String
var animation: (Double) -> Animation
var duration: Double = 2
var body: some View {
VStack {
ZStack {
Color(UIColor.systemBackground)
AnimationView(
animatableData: ratio,
history: history,
duration: duration
)
}
.onAppear {
withAnimation(animation(duration)) { ratio = 1 }
}
.onPreferenceChange(AnimationRatioKey.self) { pair in
guard let pair = pair else { return }
history.append(pair)
}
.padding(.horizontal)
Text(title)
}
}
}
private struct AnimationView: View, Animatable {
var animatableData: Double
var history: [AnimationRatio]
var duration: Double
var body: some View {
HStack(alignment: .center, spacing: 0) {
Graph(animatableData: animatableData, history: history, duration: duration)
.stroke(lineWidth: 4)
.fill(.cyan)
Ball(animatableData: animatableData)
.fill(.orange)
.frame(width: 16)
}
.preference(
key: AnimationRatioKey.self,
value: .init(value: animatableData, time: current)
)
}
private var current: TimeInterval {
Double(Int(Date().timeIntervalSinceReferenceDate * 1000))/1000
}
}
private struct Graph: Shape {
var animatableData: Double
var history: [AnimationRatio]
var duration: Double
func path(in rect: CGRect) -> Path {
let points = history.map { (pair: AnimationRatio) -> CGPoint in
let diff = min(max((pair.time - history[0].time), 0), duration) / duration
return CGPoint(x: rect.width * diff, y: -rect.midY * pair.value + 1.5 * rect.midY)
}
var path = Path()
path.addLines(points)
return path
}
}
private struct Ball: Shape {
var animatableData: Double
func path(in rect: CGRect) -> Path {
let center = CGPoint(x: rect.midX, y: -rect.midY * animatableData + 1.5 * rect.midY)
return Path.init(ellipseIn: CGRect(
origin: CGPoint(x: center.x-(rect.width/2), y: center.y-(rect.width/2)),
size: CGSize(width: rect.width, height: rect.width)
))
}
}
struct AnimationGridView_Previews: PreviewProvider {
static var previews: some View {
AnimationGridView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment