-
Notifications
You must be signed in to change notification settings - Fork 155
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Spiner animation types feature
- Loading branch information
1 parent
b9ee2b8
commit a21ff0c
Showing
11 changed files
with
593 additions
and
23 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
// | ||
// SpinerAnimationType.swift | ||
// TransitionButton | ||
// | ||
// Created by Rahul Mayani on 11/09/20. | ||
// Copyright © 2020 ITechnoDev. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import UIKit | ||
|
||
// swiftlint:disable:next class_delegate_protocol | ||
protocol TransitionButtonAnimationDelegate { | ||
|
||
func setupSpinnerAnimation(in layer: CAShapeLayer, frame: CGRect, color: UIColor, spinnerSize: UInt?) | ||
} | ||
|
||
/** | ||
Enum of animation types used for spiner animation. | ||
- DefaultSpinner: LineSpinFadeLoader animation. | ||
- BallRotate: BallRotate animation. | ||
- BallPulse: BallPulse animation. | ||
- AudioEqualizer: AudioEqualizer animation. | ||
- BallClipRotate: BallClipRotate animation. | ||
- BallScale: BallScale animation. | ||
*/ | ||
public enum SpinerAnimationType: Int, CaseIterable { | ||
|
||
/** | ||
DefaultSpinner. | ||
- returns: Instance of DefaultSpinner. | ||
*/ | ||
case defaultSpinner = 0 | ||
/** | ||
BallRotate. | ||
- returns: Instance of SpinerBallRotate. | ||
*/ | ||
case ballRotate = 1 | ||
/** | ||
BallPulse. | ||
- returns: Instance of SpinerBallPulse. | ||
*/ | ||
case ballPulse = 2 | ||
/** | ||
AudioEqualizer. | ||
- returns: Instance of SpinerAudioEqualizer. | ||
*/ | ||
case audioEqualizer = 3 | ||
/** | ||
BallClipRotate. | ||
- returns: Instance of SpinerBallClipRotate. | ||
*/ | ||
case ballClipRotate = 4 | ||
/** | ||
BallScale. | ||
- returns: Instance of SpinerBallScale. | ||
*/ | ||
case ballScale = 5 | ||
|
||
|
||
// swiftlint:disable:next cyclomatic_complexity function_body_length | ||
func animation() -> TransitionButtonAnimationDelegate { | ||
switch self { | ||
case .defaultSpinner: | ||
return DefaultSpinner() | ||
case .ballRotate: | ||
return SpinerBallRotate() | ||
case .ballPulse: | ||
return SpinerBallPulse() | ||
case .audioEqualizer: | ||
return SpinerAudioEqualizer() | ||
case .ballClipRotate: | ||
return SpinerBallClipRotate() | ||
case .ballScale: | ||
return SpinerBallScale() | ||
} | ||
} | ||
} | ||
|
||
enum TransitionButtonAnimationShape { | ||
case circle | ||
case ringTwoHalfVertical | ||
case ringTwoHalfHorizontal | ||
case line | ||
|
||
// swiftlint:disable:next cyclomatic_complexity function_body_length | ||
func layerWith(size: CGSize, color: UIColor) -> CALayer { | ||
let layer: CAShapeLayer = CAShapeLayer() | ||
var path: UIBezierPath = UIBezierPath() | ||
let lineWidth: CGFloat = 2 | ||
|
||
switch self { | ||
case .circle: | ||
path.addArc(withCenter: CGPoint(x: size.width / 2, y: size.height / 2), | ||
radius: size.width / 2, | ||
startAngle: 0, | ||
endAngle: CGFloat(2 * Double.pi), | ||
clockwise: false) | ||
layer.fillColor = color.cgColor | ||
case .ringTwoHalfVertical: | ||
path.addArc(withCenter: CGPoint(x: size.width / 2, y: size.height / 2), | ||
radius: size.width / 2, | ||
startAngle: CGFloat(-3 * Double.pi / 4), | ||
endAngle: CGFloat(-Double.pi / 4), | ||
clockwise: true) | ||
path.move( | ||
to: CGPoint(x: size.width / 2 - size.width / 2 * cos(CGFloat(Double.pi / 4)), | ||
y: size.height / 2 + size.height / 2 * sin(CGFloat(Double.pi / 4))) | ||
) | ||
path.addArc(withCenter: CGPoint(x: size.width / 2, y: size.height / 2), | ||
radius: size.width / 2, | ||
startAngle: CGFloat(-5 * Double.pi / 4), | ||
endAngle: CGFloat(-7 * Double.pi / 4), | ||
clockwise: false) | ||
layer.fillColor = nil | ||
layer.strokeColor = color.cgColor | ||
layer.lineWidth = lineWidth | ||
case .ringTwoHalfHorizontal: | ||
path.addArc(withCenter: CGPoint(x: size.width / 2, y: size.height / 2), | ||
radius: size.width / 2, | ||
startAngle: CGFloat(3 * Double.pi / 4), | ||
endAngle: CGFloat(5 * Double.pi / 4), | ||
clockwise: true) | ||
path.move( | ||
to: CGPoint(x: size.width / 2 + size.width / 2 * cos(CGFloat(Double.pi / 4)), | ||
y: size.height / 2 - size.height / 2 * sin(CGFloat(Double.pi / 4))) | ||
) | ||
path.addArc(withCenter: CGPoint(x: size.width / 2, y: size.height / 2), | ||
radius: size.width / 2, | ||
startAngle: CGFloat(-Double.pi / 4), | ||
endAngle: CGFloat(Double.pi / 4), | ||
clockwise: true) | ||
layer.fillColor = nil | ||
layer.strokeColor = color.cgColor | ||
layer.lineWidth = lineWidth | ||
case .line: | ||
path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: size.width, height: size.height), | ||
cornerRadius: size.width / 2) | ||
layer.fillColor = color.cgColor | ||
} | ||
|
||
layer.backgroundColor = nil | ||
layer.path = path.cgPath | ||
layer.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) | ||
|
||
return layer | ||
} | ||
} |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// | ||
// DefaultSpinner.swift | ||
// TransitionButton | ||
// | ||
// Created by Rahul Mayani on 11/09/20. | ||
// Copyright © 2020 ITechnoDev. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import UIKit | ||
|
||
class DefaultSpinner: TransitionButtonAnimationDelegate { | ||
|
||
/// setup spinner layer | ||
/// | ||
/// - Parameters: | ||
/// - layer: layer Parent layer (Button layer) | ||
/// - frame: frame of parant layer | ||
/// - color: color of spinner | ||
/// - spinnerSize: size of spinner layer | ||
func setupSpinnerAnimation(in layer: CAShapeLayer, frame: CGRect, color: UIColor, spinnerSize: UInt?) { | ||
|
||
self.setToFrame(frame, layer: layer) | ||
|
||
let rotate = CABasicAnimation(keyPath: "transform.rotation.z") | ||
rotate.fromValue = 0 | ||
rotate.toValue = Double.pi * 2 | ||
rotate.duration = 0.4 | ||
rotate.timingFunction = CAMediaTimingFunction(name: .linear) | ||
|
||
rotate.repeatCount = HUGE | ||
rotate.fillMode = .forwards | ||
rotate.isRemovedOnCompletion = false | ||
layer.add(rotate, forKey: rotate.keyPath) | ||
} | ||
|
||
func setToFrame(_ frame: CGRect, layer: CAShapeLayer) { | ||
let radius:CGFloat = (frame.height / 2) * 0.5 | ||
layer.frame = CGRect(x: 0, y: 0, width: frame.height, height: frame.height) | ||
let center = CGPoint(x: frame.height / 2, y: layer.bounds.center.y) | ||
let startAngle = 0 - Double.pi/2 | ||
let endAngle = Double.pi * 2 - Double.pi/2 | ||
let clockwise: Bool = true | ||
layer.path = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(startAngle), endAngle: CGFloat(endAngle), clockwise: clockwise).cgPath | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// | ||
// SpinerAudioEqualizer.swift | ||
// TransitionButton | ||
// | ||
// Created by Rahul Mayani on 11/09/20. | ||
// Copyright © 2020 ITechnoDev. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import UIKit | ||
|
||
class SpinerAudioEqualizer: TransitionButtonAnimationDelegate { | ||
|
||
func setupSpinnerAnimation(in layer: CAShapeLayer, frame: CGRect, color: UIColor, spinnerSize: UInt?) { | ||
let lineSize = frame.width / 12 | ||
let x = (layer.bounds.width - lineSize * 7) / 2 | ||
let y = (layer.bounds.height - frame.height) / 2 | ||
let duration: [CFTimeInterval] = [4.3, 2.5, 1.7, 3.1] | ||
let values = [0, 0.7, 0.4, 0.05, 0.95, 0.3, 0.9, 0.4, 0.15, 0.18, 0.75, 0.01] | ||
|
||
// Draw lines | ||
for i in 0 ..< 4 { | ||
let animation = CAKeyframeAnimation() | ||
|
||
animation.keyPath = "path" | ||
animation.isAdditive = true | ||
animation.values = [] | ||
|
||
for j in 0 ..< values.count { | ||
let heightFactor = values[j] | ||
let height = frame.height * CGFloat(heightFactor) | ||
let point = CGPoint(x: 0, y: frame.height - height) | ||
let path = UIBezierPath(rect: CGRect(origin: point, size: CGSize(width: lineSize, height: height - 20))) | ||
|
||
animation.values?.append(path.cgPath) | ||
} | ||
animation.duration = duration[i] | ||
animation.repeatCount = HUGE | ||
animation.isRemovedOnCompletion = false | ||
|
||
let line = TransitionButtonAnimationShape.line.layerWith(size: CGSize(width: lineSize, height: frame.height), color: color) | ||
let frame = CGRect(x: x + lineSize * 2 * CGFloat(i), | ||
y: y, | ||
width: lineSize, | ||
height: frame.height) | ||
|
||
line.frame = frame | ||
line.add(animation, forKey: "animation") | ||
layer.addSublayer(line) | ||
} | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// | ||
// SpinerBallClipRotate.swift | ||
// TransitionButton | ||
// | ||
// Created by Rahul Mayani on 11/09/20. | ||
// Copyright © 2020 ITechnoDev. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import UIKit | ||
|
||
class SpinerBallClipRotate: TransitionButtonAnimationDelegate { | ||
|
||
func setupSpinnerAnimation(in layer: CAShapeLayer, frame: CGRect, color: UIColor, spinnerSize: UInt?) { | ||
let bigCircleSize: CGFloat = frame.width / 1.4 | ||
let smallCircleSize: CGFloat = frame.width / 2.5 | ||
let longDuration: CFTimeInterval = 1 | ||
#if swift(>=4.2) | ||
let timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) | ||
#else | ||
let timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) | ||
#endif | ||
|
||
circleOf(shape: .ringTwoHalfHorizontal, | ||
duration: longDuration, | ||
timingFunction: timingFunction, | ||
layer: layer, | ||
size: bigCircleSize, | ||
color: color, reverse: false) | ||
circleOf(shape: .ringTwoHalfVertical, | ||
duration: longDuration, | ||
timingFunction: timingFunction, | ||
layer: layer, | ||
size: smallCircleSize, | ||
color: color, reverse: true) | ||
} | ||
|
||
func createAnimationIn(duration: CFTimeInterval, timingFunction: CAMediaTimingFunction, reverse: Bool) -> CAAnimation { | ||
// Scale animation | ||
let scaleAnimation = CAKeyframeAnimation(keyPath: "transform.scale") | ||
|
||
scaleAnimation.keyTimes = [0, 0.5, 1] | ||
scaleAnimation.timingFunctions = [timingFunction, timingFunction] | ||
scaleAnimation.values = [1, 0.6, 1] | ||
scaleAnimation.duration = duration | ||
|
||
// Rotate animation | ||
let rotateAnimation = CAKeyframeAnimation(keyPath: "transform.rotation.z") | ||
|
||
rotateAnimation.keyTimes = scaleAnimation.keyTimes | ||
rotateAnimation.timingFunctions = [timingFunction, timingFunction] | ||
if !reverse { | ||
rotateAnimation.values = [0, Double.pi, 2 * Double.pi] | ||
} else { | ||
rotateAnimation.values = [0, -Double.pi, -2 * Double.pi] | ||
} | ||
rotateAnimation.duration = duration | ||
|
||
// Animation | ||
let animation = CAAnimationGroup() | ||
|
||
animation.animations = [scaleAnimation, rotateAnimation] | ||
animation.duration = duration | ||
animation.repeatCount = HUGE | ||
animation.isRemovedOnCompletion = false | ||
|
||
return animation | ||
} | ||
|
||
// swiftlint:disable:next function_parameter_count | ||
func circleOf(shape: TransitionButtonAnimationShape, duration: CFTimeInterval, timingFunction: CAMediaTimingFunction, layer: CALayer, size: CGFloat, color: UIColor, reverse: Bool) { | ||
let circle = shape.layerWith(size: CGSize(width: size, height: size), color: color) | ||
let frame = CGRect(x: (layer.bounds.size.width - size) / 2, | ||
y: (layer.bounds.size.height - size) / 2, | ||
width: size, | ||
height: size) | ||
let animation = createAnimationIn(duration: duration, timingFunction: timingFunction, reverse: reverse) | ||
|
||
circle.frame = frame | ||
circle.add(animation, forKey: "animation") | ||
layer.addSublayer(circle) | ||
} | ||
} |
Oops, something went wrong.