ActivityIndicatorAnimationCirclePendulum.swift 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. //
  2. // ActivityIndicatorAnimationCirclePendulum.swift
  3. // IBAnimatable
  4. //
  5. // Created by phimage on 29/06/2018.
  6. // Copyright © 2018 IBAnimatable. All rights reserved.
  7. //
  8. import Foundation
  9. import UIKit
  10. public class ActivityIndicatorAnimationCirclePendulum: ActivityIndicatorAnimating {
  11. // MARK: Properties
  12. public var duration: CFTimeInterval = 0.8
  13. fileprivate let ratio: CGFloat = 7
  14. fileprivate let ballCount: Int = 3
  15. #if LG
  16. public var primaryColor = true
  17. #else
  18. public var primaryColor = false
  19. #endif
  20. fileprivate let colors: [UIColor] = [.magenta, .yellow, .cyan]
  21. fileprivate var ballSize: CGFloat = 0
  22. // MARK: ActivityIndicatorAnimating
  23. public func configureAnimation(in layer: CALayer, size: CGSize, color: UIColor) {
  24. ballSize = size.height / ratio
  25. var xPos = (size.width - (CGFloat(ballCount) * ballSize)) / 2
  26. let yPos = size.height - ballSize / 2
  27. for i in 0 ..< ballCount {
  28. let ball = ActivityIndicatorShape.circle.makeLayer(size: CGSize(width: ballSize, height: ballSize), color: primaryColor ? colors[i]: color)
  29. ball.frame = CGRect(x: xPos, y: yPos, width: ballSize, height: ballSize)
  30. ball.add(makeAnimation(for: ball, in: layer, size: size, pos: i), forKey: "animation")
  31. layer.addSublayer(ball)
  32. xPos += ballSize
  33. }
  34. }
  35. }
  36. private extension ActivityIndicatorAnimationCirclePendulum {
  37. func makeAnimation(for ball: CALayer, in layer: CALayer, size: CGSize, pos: Int) -> CAAnimation {
  38. let angle = 2 * atan(ballSize / (size.height - ballSize / 2))
  39. var animations: [CAKeyframeAnimation] = [] // only 3 balls, code not generic yet, how to compute angles?
  40. let rotateAnimation = CAKeyframeAnimation(keyPath: .position)
  41. var animPos = 0
  42. rotateAnimation.path = UIBezierPath(arcCenter: CGPoint(x: size.width / 2, y: size.height / 2),
  43. radius: size.height / 2,
  44. startAngle: CGFloat.pi / 2 + angle,
  45. endAngle: 2 * CGFloat.pi + CGFloat.pi / 2 - angle,
  46. clockwise: true).cgPath
  47. rotateAnimation.duration = duration
  48. rotateAnimation.timingFunctionType = .easeInOut
  49. rotateAnimation.beginTime = duration * CFTimeInterval((pos + animPos) % ballCount)
  50. animations.append(rotateAnimation)
  51. let rotateAnimation2 = CAKeyframeAnimation(keyPath: .position)
  52. animPos += 1
  53. rotateAnimation2.path = UIBezierPath(arcCenter: CGPoint(x: size.width / 2, y: size.height / 2),
  54. radius: size.height / 2,
  55. startAngle: 2 * CGFloat.pi + CGFloat.pi / 2 - angle,
  56. endAngle: 2 * CGFloat.pi + CGFloat.pi / 2,
  57. clockwise: true).cgPath
  58. rotateAnimation2.duration = duration
  59. rotateAnimation2.timingFunctionType = .easeOutExpo
  60. rotateAnimation2.beginTime = duration * CFTimeInterval((pos + animPos) % ballCount)
  61. animations.append(rotateAnimation2)
  62. let rotateAnimation3 = CAKeyframeAnimation(keyPath: .position)
  63. animPos += 1
  64. rotateAnimation3.path = UIBezierPath(arcCenter: CGPoint(x: size.width / 2, y: size.height / 2),
  65. radius: size.height / 2,
  66. startAngle: 2 * CGFloat.pi + CGFloat.pi / 2,
  67. endAngle: 2 * CGFloat.pi + CGFloat.pi / 2 + angle,
  68. clockwise: true).cgPath
  69. rotateAnimation3.duration = duration
  70. rotateAnimation3.timingFunctionType = .easeOutExpo
  71. rotateAnimation3.beginTime = duration * CFTimeInterval((pos + animPos) % ballCount)
  72. animations.append(rotateAnimation3)
  73. let animation = CAAnimationGroup()
  74. animation.animations = animations
  75. animation.duration = duration * CFTimeInterval(ballCount)
  76. animation.repeatCount = .infinity
  77. animation.isRemovedOnCompletion = false
  78. return animation
  79. }
  80. }