ActivityIndicatorAnimationOrbit.swift 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. //
  2. // Created by Tom Baranes on 23/08/16.
  3. // Copyright © 2016 IBAnimatable. All rights reserved.
  4. //
  5. import UIKit
  6. public class ActivityIndicatorAnimationOrbit: ActivityIndicatorAnimating {
  7. // MARK: Properties
  8. fileprivate let duration: CFTimeInterval = 1.9
  9. fileprivate let satelliteCoreRatio: CGFloat = 0.25
  10. fileprivate let distanceRatio: CGFloat = 1.5
  11. fileprivate var coreSize: CGFloat = 0
  12. fileprivate var satelliteSize: CGFloat = 0
  13. fileprivate var size: CGSize = .zero
  14. // MARK: ActivityIndicatorAnimating
  15. public func configureAnimation(in layer: CALayer, size: CGSize, color: UIColor) {
  16. self.size = size
  17. coreSize = size.width / (1 + satelliteCoreRatio + distanceRatio)
  18. satelliteSize = coreSize * satelliteCoreRatio
  19. animateRing1(in: layer, color: color)
  20. animateRing2(in: layer, color: color)
  21. animateCore(in: layer, color: color)
  22. animateSatellite(in: layer, color: color)
  23. }
  24. }
  25. // MARK: - Satellite
  26. private extension ActivityIndicatorAnimationOrbit {
  27. func animateSatellite(in layer: CALayer, color: UIColor) {
  28. let rotateAnimation = makeSatelliteRotateAnimation(layer: layer)
  29. let circle = ActivityIndicatorShape.circle.makeLayer(size: CGSize(width: satelliteSize, height: satelliteSize), color: color)
  30. let frame = CGRect(x: 0, y: 0, width: satelliteSize, height: satelliteSize)
  31. circle.frame = frame
  32. circle.add(rotateAnimation, forKey: "animation")
  33. layer.addSublayer(circle)
  34. }
  35. func makeSatelliteRotateAnimation(layer: CALayer) -> CAKeyframeAnimation {
  36. let rotateAnimation = CAKeyframeAnimation(keyPath: .position)
  37. rotateAnimation.path = UIBezierPath(arcCenter: CGPoint(x: layer.bounds.midX, y: layer.bounds.midY),
  38. radius: (size.width - satelliteSize) / 2,
  39. startAngle: CGFloat.pi * 1.5,
  40. endAngle: CGFloat.pi * 1.5 + 4 * CGFloat.pi,
  41. clockwise: true).cgPath
  42. rotateAnimation.duration = duration * 2
  43. rotateAnimation.repeatCount = .infinity
  44. rotateAnimation.isRemovedOnCompletion = false
  45. return rotateAnimation
  46. }
  47. }
  48. // MARK: - Core
  49. private extension ActivityIndicatorAnimationOrbit {
  50. func animateCore(in layer: CALayer, color: UIColor) {
  51. let circle = ActivityIndicatorShape.circle.makeLayer(size: CGSize(width: coreSize, height: coreSize), color: color)
  52. let frame = CGRect(x: (layer.bounds.size.width - coreSize) / 2,
  53. y: (layer.bounds.size.height - coreSize) / 2,
  54. width: coreSize,
  55. height: coreSize)
  56. circle.frame = frame
  57. circle.add(coreScaleAnimation, forKey: "animation")
  58. layer.addSublayer(circle)
  59. }
  60. var coreScaleAnimation: CAKeyframeAnimation {
  61. let inTimingFunction: TimingFunctionType = .custom((0.7, 0), (1, 0.5))
  62. let outTimingFunction: TimingFunctionType = .custom(( 0, 0.7), (0.5, 1))
  63. let standByTimingFunction: TimingFunctionType = .linear
  64. let scaleAnimation = CAKeyframeAnimation(keyPath: .scale)
  65. scaleAnimation.keyTimes = [0, 0.45, 0.55, 1]
  66. scaleAnimation.timingFunctionsType = [inTimingFunction, standByTimingFunction, outTimingFunction]
  67. scaleAnimation.values = [1, 1.3, 1.3, 1]
  68. scaleAnimation.duration = duration
  69. scaleAnimation.repeatCount = .infinity
  70. scaleAnimation.isRemovedOnCompletion = false
  71. return scaleAnimation
  72. }
  73. }
  74. // MARK: - Ring 1
  75. private extension ActivityIndicatorAnimationOrbit {
  76. func animateRing1(in layer: CALayer, color: UIColor) {
  77. let animation = ring1Animation
  78. let circle = ActivityIndicatorShape.circle.makeLayer(size: CGSize(width: coreSize, height: coreSize), color: color)
  79. let frame = CGRect(x: (layer.bounds.size.width - coreSize) / 2,
  80. y: (layer.bounds.size.height - coreSize) / 2,
  81. width: coreSize,
  82. height: coreSize)
  83. circle.frame = frame
  84. circle.add(animation, forKey: "animation")
  85. layer.addSublayer(circle)
  86. }
  87. var ring1Animation: CAAnimationGroup {
  88. let animation = CAAnimationGroup()
  89. animation.animations = [ring1ScaleAnimation, ring1OpacityAnimation]
  90. animation.duration = duration
  91. animation.repeatCount = .infinity
  92. animation.isRemovedOnCompletion = false
  93. return animation
  94. }
  95. var ring1ScaleAnimation: CAKeyframeAnimation {
  96. let scaleAnimation = CAKeyframeAnimation(keyPath: .scale)
  97. scaleAnimation.keyTimes = [0, 0.45, 0.45, 1]
  98. scaleAnimation.timingFunctionType = .linear
  99. scaleAnimation.values = [0, 0, 1.3, 2]
  100. scaleAnimation.duration = duration
  101. return scaleAnimation
  102. }
  103. var ring1OpacityAnimation: CAKeyframeAnimation {
  104. let opacityAnimation = CAKeyframeAnimation(keyPath: .opacity)
  105. opacityAnimation.keyTimes = [0, 0.45, 1]
  106. opacityAnimation.timingFunctionsType = [.linear, .easeOutExpo]
  107. opacityAnimation.values = [0.8, 0.8, 0]
  108. opacityAnimation.duration = duration
  109. return opacityAnimation
  110. }
  111. }
  112. // MARK: - Ring 2
  113. private extension ActivityIndicatorAnimationOrbit {
  114. func animateRing2(in layer: CALayer, color: UIColor) {
  115. let animation = ring2Animation
  116. let circle = ActivityIndicatorShape.circle.makeLayer(size: CGSize(width: coreSize, height: coreSize), color: color)
  117. let frame = CGRect(x: (layer.bounds.size.width - coreSize) / 2,
  118. y: (layer.bounds.size.height - coreSize) / 2,
  119. width: coreSize,
  120. height: coreSize)
  121. circle.frame = frame
  122. circle.add(animation, forKey: "animation")
  123. layer.addSublayer(circle)
  124. }
  125. var ring2Animation: CAAnimationGroup {
  126. let animation = CAAnimationGroup()
  127. animation.animations = [ring2ScaleAnimation, ring2OpacityAnimation]
  128. animation.duration = duration
  129. animation.repeatCount = .infinity
  130. animation.isRemovedOnCompletion = false
  131. return animation
  132. }
  133. var ring2ScaleAnimation: CAKeyframeAnimation {
  134. let scaleAnimation = CAKeyframeAnimation(keyPath: .scale)
  135. scaleAnimation.keyTimes = [0, 0.55, 0.55, 1]
  136. scaleAnimation.timingFunctionType = .linear
  137. scaleAnimation.values = [0, 0, 1.3, 2.1]
  138. scaleAnimation.duration = duration
  139. return scaleAnimation
  140. }
  141. var ring2OpacityAnimation: CAKeyframeAnimation {
  142. let opacityAnimation = CAKeyframeAnimation(keyPath: .opacity)
  143. opacityAnimation.keyTimes = [0, 0.55, 0.65, 1]
  144. opacityAnimation.timingFunctionsType = [.linear, .easeOutExpo]
  145. opacityAnimation.values = [0.7, 0.7, 0, 0]
  146. opacityAnimation.duration = duration
  147. return opacityAnimation
  148. }
  149. }