// // ActivityIndicatorAnimationCirclePendulum.swift // IBAnimatable // // Created by phimage on 29/06/2018. // Copyright © 2018 IBAnimatable. All rights reserved. // import Foundation import UIKit public class ActivityIndicatorAnimationCirclePendulum: ActivityIndicatorAnimating { // MARK: Properties public var duration: CFTimeInterval = 0.8 fileprivate let ratio: CGFloat = 7 fileprivate let ballCount: Int = 3 #if LG public var primaryColor = true #else public var primaryColor = false #endif fileprivate let colors: [UIColor] = [.magenta, .yellow, .cyan] fileprivate var ballSize: CGFloat = 0 // MARK: ActivityIndicatorAnimating public func configureAnimation(in layer: CALayer, size: CGSize, color: UIColor) { ballSize = size.height / ratio var xPos = (size.width - (CGFloat(ballCount) * ballSize)) / 2 let yPos = size.height - ballSize / 2 for i in 0 ..< ballCount { let ball = ActivityIndicatorShape.circle.makeLayer(size: CGSize(width: ballSize, height: ballSize), color: primaryColor ? colors[i]: color) ball.frame = CGRect(x: xPos, y: yPos, width: ballSize, height: ballSize) ball.add(makeAnimation(for: ball, in: layer, size: size, pos: i), forKey: "animation") layer.addSublayer(ball) xPos += ballSize } } } private extension ActivityIndicatorAnimationCirclePendulum { func makeAnimation(for ball: CALayer, in layer: CALayer, size: CGSize, pos: Int) -> CAAnimation { let angle = 2 * atan(ballSize / (size.height - ballSize / 2)) var animations: [CAKeyframeAnimation] = [] // only 3 balls, code not generic yet, how to compute angles? let rotateAnimation = CAKeyframeAnimation(keyPath: .position) var animPos = 0 rotateAnimation.path = UIBezierPath(arcCenter: CGPoint(x: size.width / 2, y: size.height / 2), radius: size.height / 2, startAngle: CGFloat.pi / 2 + angle, endAngle: 2 * CGFloat.pi + CGFloat.pi / 2 - angle, clockwise: true).cgPath rotateAnimation.duration = duration rotateAnimation.timingFunctionType = .easeInOut rotateAnimation.beginTime = duration * CFTimeInterval((pos + animPos) % ballCount) animations.append(rotateAnimation) let rotateAnimation2 = CAKeyframeAnimation(keyPath: .position) animPos += 1 rotateAnimation2.path = UIBezierPath(arcCenter: CGPoint(x: size.width / 2, y: size.height / 2), radius: size.height / 2, startAngle: 2 * CGFloat.pi + CGFloat.pi / 2 - angle, endAngle: 2 * CGFloat.pi + CGFloat.pi / 2, clockwise: true).cgPath rotateAnimation2.duration = duration rotateAnimation2.timingFunctionType = .easeOutExpo rotateAnimation2.beginTime = duration * CFTimeInterval((pos + animPos) % ballCount) animations.append(rotateAnimation2) let rotateAnimation3 = CAKeyframeAnimation(keyPath: .position) animPos += 1 rotateAnimation3.path = UIBezierPath(arcCenter: CGPoint(x: size.width / 2, y: size.height / 2), radius: size.height / 2, startAngle: 2 * CGFloat.pi + CGFloat.pi / 2, endAngle: 2 * CGFloat.pi + CGFloat.pi / 2 + angle, clockwise: true).cgPath rotateAnimation3.duration = duration rotateAnimation3.timingFunctionType = .easeOutExpo rotateAnimation3.beginTime = duration * CFTimeInterval((pos + animPos) % ballCount) animations.append(rotateAnimation3) let animation = CAAnimationGroup() animation.animations = animations animation.duration = duration * CFTimeInterval(ballCount) animation.repeatCount = .infinity animation.isRemovedOnCompletion = false return animation } }