Created
July 22, 2023 15:43
-
-
Save Koshimizu-Takehito/b6c87f4a30f7a58dddd35fcd0b73c16b to your computer and use it in GitHub Desktop.
CustomAnimationをグラフで描画
This file contains 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
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