KeyframeAnimation.swift 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. //
  2. // KeyFrameAnimation.swift
  3. // LoaderUI
  4. //
  5. // Created by Vinh Nguyen on 5/3/20.
  6. // Copyright © 2020 Vinh Nguyen. All rights reserved.
  7. //
  8. import SwiftUI
  9. struct Ring: Shape {
  10. func path(in rect: CGRect) -> Path {
  11. let dimension = min(rect.size.width, rect.size.height)
  12. let lineWidth = dimension / 32
  13. let path = Path(ellipseIn: rect)
  14. return path.strokedPath(StrokeStyle(lineWidth: lineWidth, lineCap: .round))
  15. }
  16. }
  17. struct KeyframeAnimation: AnimatableModifier {
  18. typealias OnCompleteHandler = (Int) -> Void
  19. private let keyframe: Int
  20. private var progressiveKeyframe: Double
  21. private let onComplete: OnCompleteHandler
  22. init(keyframe: Double, onComplete: @escaping OnCompleteHandler) {
  23. self.keyframe = Int(keyframe)
  24. self.progressiveKeyframe = keyframe
  25. self.onComplete = onComplete
  26. }
  27. var animatableData: Double {
  28. get { progressiveKeyframe }
  29. set {
  30. progressiveKeyframe = newValue
  31. if Int(progressiveKeyframe) == keyframe {
  32. onComplete(keyframe)
  33. }
  34. }
  35. }
  36. func body(content: Content) -> some View {
  37. content
  38. }
  39. }
  40. enum TimingFunction {
  41. case timingCurve(c0x: Double, c0y: Double, c1x: Double, c1y: Double)
  42. case linear
  43. case easeInOut
  44. func animation(duration: Double) -> Animation {
  45. switch self {
  46. case let .timingCurve(c0x, c0y, c1x, c1y):
  47. return .timingCurve(c0x, c0y, c1x, c1y, duration: duration)
  48. case .linear:
  49. return .linear(duration: duration)
  50. case .easeInOut:
  51. return .easeInOut(duration: duration)
  52. }
  53. }
  54. }
  55. class KeyframeIterator: IteratorProtocol {
  56. typealias Element = (Int, Animation, Animation?, Bool)
  57. private let beginTime: Double
  58. private let duration: Double
  59. private let timingFunctions: [TimingFunction]
  60. private let keyTimes: [Double]
  61. private let durations: [Double]
  62. private let animations: [Animation]
  63. private var keyframe: Int = 0
  64. private var isRepeating = false
  65. init(beginTime: Double,
  66. duration: Double,
  67. timingFunctions: [TimingFunction],
  68. keyTimes: [Double]
  69. ) {
  70. self.beginTime = beginTime
  71. self.duration = duration
  72. self.timingFunctions = timingFunctions
  73. self.keyTimes = keyTimes
  74. assert(keyTimes.count - timingFunctions.count == 1)
  75. let keyPercents = zip(keyTimes[0..<keyTimes.count - 1], keyTimes[1...])
  76. .map { $1 - $0 }
  77. let durations = keyPercents.map { duration * $0 }
  78. self.durations = durations + [0]
  79. animations = zip(durations, timingFunctions).map { duration, timingFunction in
  80. timingFunction.animation(duration: duration)
  81. }
  82. }
  83. func next() -> Element? {
  84. let isFirst = keyframe == 0
  85. let isLast = keyframe == keyTimes.count - 1
  86. let delay = isFirst && !isRepeating ? beginTime : 0
  87. let keyframeTracker = Animation.linear(duration: durations[keyframe]).delay(delay)
  88. let animation = isLast ? nil : animations[keyframe].delay(delay)
  89. let nextKeyframe = isLast ? 0 : keyframe + 1
  90. let element: Element = (nextKeyframe, keyframeTracker, animation, isLast)
  91. if isLast {
  92. isRepeating = true
  93. }
  94. keyframe = nextKeyframe
  95. return element
  96. }
  97. }
  98. struct KeyframeAnimationController<T: View>: View {
  99. typealias Content = (Int) -> T
  100. @State private var keyframe: Double = 0
  101. @State private var animation: Animation?
  102. private let beginTime: Double
  103. private let duration: Double
  104. private let timingFunctions: [TimingFunction]
  105. private let keyTimes: [Double]
  106. private let keyframeIterator: KeyframeIterator
  107. private var content: Content
  108. var body: some View {
  109. content(Int(keyframe))
  110. .animation(animation)
  111. .modifier(KeyframeAnimation(keyframe: self.keyframe, onComplete: handleComplete))
  112. .onAppear {
  113. self.nextKeyframe()
  114. }
  115. }
  116. init(beginTime: Double,
  117. duration: Double,
  118. timingFunctions: [TimingFunction],
  119. keyTimes: [Double],
  120. content: @escaping Content) {
  121. self.beginTime = beginTime
  122. self.duration = duration
  123. self.timingFunctions = timingFunctions
  124. self.keyTimes = keyTimes
  125. keyframeIterator = KeyframeIterator(beginTime: beginTime,
  126. duration: duration,
  127. timingFunctions: timingFunctions,
  128. keyTimes: keyTimes)
  129. self.content = content
  130. }
  131. private func handleComplete(_ keyframe: Int) {
  132. nextKeyframe()
  133. }
  134. private func nextKeyframe() {
  135. DispatchQueue.main.async {
  136. guard let data = self.keyframeIterator.next() else {
  137. return
  138. }
  139. let (keyframe, keyframeTracker, animation, _) = data
  140. self.animation = animation
  141. withAnimation(keyframeTracker) {
  142. self.keyframe = Double(keyframe)
  143. }
  144. }
  145. }
  146. }