J'essaie de créer une animation de chargeur de cercle tournant comme dans le projet Android suivant ( https://github.com/pedant/sweet-alert-dialog ).
Je n'ai pas besoin de l'intégralité de la boîte de dialogue contextuelle - seulement de la partie tournante. Il change de couleur et tourne indéfiniment (jusqu'à ce que je décide de le rejeter).
Je suis un peu nouveau chez Swift et je n’ai jamais été du genre à faire des animations. Voici ce que j'ai jusqu'à présent (code trouvé dans un projet similaire pour iOS):
La configuration des calques:
outlineLayer.position = CGPointMake(0,
0);
outlineLayer.path = outlineCircle
outlineLayer.fillColor = UIColor.clearColor().CGColor;
outlineLayer.strokeColor = UIColor(red: 150.0/255.0, green: 216.0/255.0, blue: 115.0/255.0, alpha: 1.0).CGColor;
outlineLayer.lineCap = kCALineCapRound
outlineLayer.lineWidth = 4;
outlineLayer.opacity = 0.1
self.layer.addSublayer(outlineLayer)
circleLayer.position = CGPointMake(0,
0);
circleLayer.path = path
circleLayer.fillColor = UIColor.clearColor().CGColor;
circleLayer.strokeColor = UIColor(red: 150.0/255.0, green: 216.0/255.0, blue: 115.0/255.0, alpha: 1.0).CGColor;
circleLayer.lineCap = kCALineCapRound
circleLayer.lineWidth = 4;
circleLayer.actions = [
"strokeStart": NSNull(),
"strokeEnd": NSNull(),
"transform": NSNull()
]
self.layer.addSublayer(circleLayer)
Animation:
let strokeStart = CABasicAnimation(keyPath: "strokeStart")
let strokeEnd = CABasicAnimation(keyPath: "strokeEnd")
let factor = 0.545
let timing = CAMediaTimingFunction(controlPoints: 0.3, 0.6, 0.8, 1.2)
strokeEnd.fromValue = 0.00
strokeEnd.toValue = 0.93
strokeEnd.duration = 10.0 * factor
strokeEnd.timingFunction = timing
strokeEnd.autoreverses = true
strokeStart.fromValue = 0.0
strokeStart.toValue = 0.68
strokeStart.duration = 10.0 * factor
strokeStart.beginTime = CACurrentMediaTime() + 3.0 * factor
strokeStart.fillMode = kCAFillModeBackwards
strokeStart.timingFunction = timing
strokeStart.repeatCount = HUGE
circleLayer.strokeStart = 0.68
circleLayer.strokeEnd = 0.93
self.circleLayer.addAnimation(strokeEnd, forKey: "strokeEnd")
self.circleLayer.addAnimation(strokeStart, forKey: "strokeStart")
mais ce que j’ai n’est pas proche et je ne sais pas du tout où aller. Ce que je fais, c’est changer une valeur et courir en constatant les conséquences, mais j’ai le sentiment d’être perdu.
Comment puis-je réaliser une telle animation comme dans l'exemple?
Je n'ai pas analysé de près les paramètres exacts de l'animation, mais cela me semble bien:
import UIKit
@IBDesignable
class SpinnerView : UIView {
override var layer: CAShapeLayer {
get {
return super.layer as! CAShapeLayer
}
}
override class var layerClass: AnyClass {
return CAShapeLayer.self
}
override func layoutSubviews() {
super.layoutSubviews()
layer.fillColor = nil
layer.strokeColor = UIColor.black.cgColor
layer.lineWidth = 3
setPath()
}
override func didMoveToWindow() {
animate()
}
private func setPath() {
layer.path = UIBezierPath(ovalIn: bounds.insetBy(dx: layer.lineWidth / 2, dy: layer.lineWidth / 2)).cgPath
}
struct Pose {
let secondsSincePriorPose: CFTimeInterval
let start: CGFloat
let length: CGFloat
init(_ secondsSincePriorPose: CFTimeInterval, _ start: CGFloat, _ length: CGFloat) {
self.secondsSincePriorPose = secondsSincePriorPose
self.start = start
self.length = length
}
}
class var poses: [Pose] {
get {
return [
Pose(0.0, 0.000, 0.7),
Pose(0.6, 0.500, 0.5),
Pose(0.6, 1.000, 0.3),
Pose(0.6, 1.500, 0.1),
Pose(0.2, 1.875, 0.1),
Pose(0.2, 2.250, 0.3),
Pose(0.2, 2.625, 0.5),
Pose(0.2, 3.000, 0.7),
]
}
}
func animate() {
var time: CFTimeInterval = 0
var times = [CFTimeInterval]()
var start: CGFloat = 0
var rotations = [CGFloat]()
var strokeEnds = [CGFloat]()
let poses = type(of: self).poses
let totalSeconds = poses.reduce(0) { $0 + $1.secondsSincePriorPose }
for pose in poses {
time += pose.secondsSincePriorPose
times.append(time / totalSeconds)
start = pose.start
rotations.append(start * 2 * .pi)
strokeEnds.append(pose.length)
}
times.append(times.last!)
rotations.append(rotations[0])
strokeEnds.append(strokeEnds[0])
animateKeyPath(keyPath: "strokeEnd", duration: totalSeconds, times: times, values: strokeEnds)
animateKeyPath(keyPath: "transform.rotation", duration: totalSeconds, times: times, values: rotations)
animateStrokeHueWithDuration(duration: totalSeconds * 5)
}
func animateKeyPath(keyPath: String, duration: CFTimeInterval, times: [CFTimeInterval], values: [CGFloat]) {
let animation = CAKeyframeAnimation(keyPath: keyPath)
animation.keyTimes = times as [NSNumber]?
animation.values = values
animation.calculationMode = .linear
animation.duration = duration
animation.repeatCount = Float.infinity
layer.add(animation, forKey: animation.keyPath)
}
func animateStrokeHueWithDuration(duration: CFTimeInterval) {
let count = 36
let animation = CAKeyframeAnimation(keyPath: "strokeColor")
animation.keyTimes = (0 ... count).map { NSNumber(value: CFTimeInterval($0) / CFTimeInterval(count)) }
animation.values = (0 ... count).map {
UIColor(hue: CGFloat($0) / CGFloat(count), saturation: 1, brightness: 1, alpha: 1).cgColor
}
animation.duration = duration
animation.calculationMode = .linear
animation.repeatCount = Float.infinity
layer.add(animation, forKey: animation.keyPath)
}
}
Essayez très simplement mes trois écrans de chargeur personnalisés:
Écrire ci-dessous le code dans le fichier Viewcontoller.Swift
class ViewController: UIViewController {
var signView = SignView(frame: CGRect.zero)
var testView = TestView(frame: CGRect.zero)
var testView1 = TestView1(frame: CGRect.zero)
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.orange
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
addSignView()
//addTestView()
//addTestView1()
}
func addSignView() {
signView.frame = CGRect(x: 0,
y: 0,
width: UIScreen.main.bounds.size.width,
height: UIScreen.main.bounds.size.height)
self.view.addSubview(signView)
signView.addAnimationLayer()
}
func addTestView() {
let boxSize: CGFloat = 200.0
testView.frame = CGRect(x: 16,
y: 350,
width: boxSize,
height: boxSize)
self.view.addSubview(testView)
testView.addAnimationLayer()
}
func addTestView1() {
testView1.frame = CGRect(x: 0,
y: 0,
width: UIScreen.main.bounds.size.width,
height: UIScreen.main.bounds.size.height)
self.view.addSubview(testView1)
testView1.addAnimationLayer()
}}
Ajoutez maintenant 3 fichiers hérités avec UiView nommé> SignView, TestView et TestView1
Code pour le fichier SignView.Swift
class SignView: UIView {
let upCircleLayer = CAShapeLayer.init()
var path = UIBezierPath.init()
var animationDuration : Double = 2
var frameHeight : CGFloat = 50.0
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.black.withAlphaComponent(0.5)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var signWavePath : UIBezierPath {
var clockCycle = true
let yPoint = self.frame.size.height/2
frameHeight = self.frame.size.width/6
for x in 1...24{
if x%2 != 0 {
let xpath = UIBezierPath(arcCenter: CGPoint(x: CGFloat(x)*frameHeight/2, y: yPoint),
radius: frameHeight/2,
startAngle: 180.0 * .pi/180.0,
endAngle: 0.0,
clockwise: clockCycle)
path.append(xpath)
if(clockCycle){
clockCycle = false
}
else{
clockCycle = true
}
}
}
return path;
}
func addAnimationLayer() {
// Add Upper Circle Layer
upCircleLayer.fillColor = UIColor.clear.cgColor
upCircleLayer.strokeColor = UIColor.white.cgColor
upCircleLayer.lineWidth = 8.0
upCircleLayer.path = signWavePath.cgPath
layer.addSublayer(upCircleLayer)
animateStrokeUpCircle()
Timer.scheduledTimer(timeInterval: animationDuration, target: self, selector: #selector(animateStrokeUpCircle), userInfo: nil, repeats: true)
}
func animateStrokeUpCircle() {
let strokeAnimation: CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
strokeAnimation.fromValue = 0.0
strokeAnimation.toValue = 1.0
strokeAnimation.duration = animationDuration
strokeAnimation.isRemovedOnCompletion = false
upCircleLayer.add(strokeAnimation, forKey: nil)
expand1()
}
func expand1() {
let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "position")
expandAnimation.fromValue = [0,sin(self.frame.width)]
expandAnimation.toValue = [-self.frame.width,cos(self.frame.width)]
expandAnimation.duration = animationDuration
expandAnimation.fillMode = kCAFillModeForwards
expandAnimation.isRemovedOnCompletion = false
upCircleLayer.add(expandAnimation, forKey: nil)
}
}
Code pour le fichier TestView:
class TestView: UIView {
let upCircleLayer = CAShapeLayer.init()
let downCircleLayer = CAShapeLayer.init()
var path1 = UIBezierPath.init()
var path2 = UIBezierPath.init()
var animationDirection : Bool = true
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var up1Circle: UIBezierPath {
return UIBezierPath(arcCenter: CGPoint(x: self.frame.size.width/4, y: self.frame.size.height/2),
radius: self.frame.size.height/4,
startAngle: 180.0 * .pi/180.0,
endAngle: 0.0,
clockwise: true)
}
var down2Circle: UIBezierPath {
return UIBezierPath(arcCenter: CGPoint(x: 3*self.frame.size.width/4, y: self.frame.size.height/2),
radius: self.frame.size.height/4,
startAngle: 180.0 * .pi/180.0,
endAngle: 0.0,
clockwise: false)
}
var up22Circle: UIBezierPath {
return UIBezierPath(arcCenter: CGPoint(x: 3*self.frame.size.width/4, y: self.frame.size.height/2),
radius: self.frame.size.height/4,
startAngle: 180.0 * .pi/180.0,
endAngle: 0.0,
clockwise: true)
}
var down11Circle: UIBezierPath {
return UIBezierPath(arcCenter: CGPoint(x: self.frame.size.width/4, y: self.frame.size.height/2),
radius: self.frame.size.height/4,
startAngle: 180.0 * .pi/180.0,
endAngle: 0.0,
clockwise: false)
}
var up2Circle: UIBezierPath {
return UIBezierPath(arcCenter: CGPoint(x: 3*self.frame.size.width/4, y: self.frame.size.height/2),
radius: self.frame.size.height/4,
startAngle: 0.0,
endAngle: 180.0 * .pi/180.0,
clockwise: true)
}
var down1Circle: UIBezierPath {
return UIBezierPath(arcCenter: CGPoint(x: self.frame.size.width/4, y: self.frame.size.height/2),
radius: self.frame.size.height/4,
startAngle: 0.0,
endAngle: 180.0 * .pi/180.0,
clockwise: false)
}
func addAnimationLayer() {
path1.append(up1Circle);
path1.append(down2Circle);
path2.append(down11Circle)
path2.append(up22Circle)
// Add Upper Circle Layer
upCircleLayer.fillColor = UIColor.clear.cgColor
upCircleLayer.strokeColor = UIColor.black.cgColor
upCircleLayer.lineWidth = 8.0
upCircleLayer.path = path1.cgPath
layer.addSublayer(upCircleLayer)
Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(expand1), userInfo: nil, repeats: true)
}
func expand() {
if animationDirection{
//upCircleLayer.path = path1.cgPath
let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
expandAnimation.fromValue = path1.cgPath
expandAnimation.toValue = path2.cgPath
expandAnimation.duration = 1.5
//expandAnimation.fillMode = kCAFillModeForwards
expandAnimation.isRemovedOnCompletion = false
upCircleLayer.add(expandAnimation, forKey: nil)
animationDirection = false
}
else{
//upCircleLayer.path = path2.cgPath
let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
expandAnimation.fromValue = path2.cgPath
expandAnimation.toValue = path1.cgPath
expandAnimation.duration = 1.5
//expandAnimation.fillMode = kCAFillModeForwards
expandAnimation.isRemovedOnCompletion = false
upCircleLayer.add(expandAnimation, forKey: nil)
animationDirection = true
}
}
func expand1() {
let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "position")
expandAnimation.fromValue = [0,self.frame.height/2]
expandAnimation.toValue = 500
expandAnimation.duration = 2.0
expandAnimation.fillMode = kCAFillModeForwards
expandAnimation.isRemovedOnCompletion = false
upCircleLayer.add(expandAnimation, forKey: nil)
}
}
Et code pour le fichier TestView1.Swift
classe TestView1: UIView {
let animationLayer = CAShapeLayer.init()
var path1 = UIBezierPath.init()
var path2 = UIBezierPath.init()
var path = UIBezierPath.init()
var circleRadius : CGFloat = 26.0;
var centerLineHeight : CGFloat = 40.0
var animationDuration : Double = 2.0
var animationDirection : Bool = true
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.black
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var centerMainLine: UIBezierPath {
let frameSize = self.frame.size
let centerLine = UIBezierPath()
centerLine.move(to: CGPoint(x: frameSize.width/2, y: frameSize.height/2 - centerLineHeight/2))
centerLine.addLine(to: CGPoint(x: frameSize.width/2, y: frameSize.height/2 + centerLineHeight/2))
return centerLine
}
var upLeftCircle: UIBezierPath {
let frameSize = self.frame.size
let halfCircle = UIBezierPath(arcCenter: CGPoint(x: frameSize.width/2 - circleRadius, y: frameSize.height/2 - centerLineHeight/2),
radius: circleRadius,
startAngle: 180.0 * .pi/180.0,
endAngle: 0.0,
clockwise: true)
return halfCircle
}
var upRightCircle: UIBezierPath {
let frameSize = self.frame.size
let halfCircle = UIBezierPath(arcCenter: CGPoint(x: frameSize.width/2 + circleRadius, y: frameSize.height/2 - centerLineHeight/2),
radius: circleRadius,
startAngle: 180.0 * .pi/180.0,
endAngle: 0.0,
clockwise: true)
return halfCircle
}
var downLeftCircle: UIBezierPath {
let frameSize = self.frame.size
let halfCircle = UIBezierPath(arcCenter: CGPoint(x: frameSize.width/2 - circleRadius, y: frameSize.height/2 + centerLineHeight/2),
radius: circleRadius,
startAngle: 180.0 * .pi/180.0,
endAngle: 0.0,
clockwise: false)
return halfCircle
}
var downRightCircle: UIBezierPath {
let frameSize = self.frame.size
let halfCircle = UIBezierPath(arcCenter: CGPoint(x: frameSize.width/2 + circleRadius, y: frameSize.height/2 + centerLineHeight/2),
radius: circleRadius,
startAngle: 180.0 * .pi/180.0,
endAngle: 0.0,
clockwise: false)
return halfCircle
}
func drawUpCircle(centerPoint:CGPoint, radiusValue:CGFloat) -> UIBezierPath {
let halfCircle = UIBezierPath(arcCenter: centerPoint,
radius: radiusValue,
startAngle: 180.0 * .pi/180.0,
endAngle: 0.0,
clockwise: true)
return halfCircle
}
func drawDownCircle(centerPoint:CGPoint,radiusValue:CGFloat) -> UIBezierPath {
let halfCircle = UIBezierPath(arcCenter: centerPoint,
radius: radiusValue,
startAngle: 180.0 * .pi/180.0,
endAngle: 0.0,
clockwise: false)
return halfCircle
}
func drawLine(fromPoint:CGPoint,toPoint:CGPoint) -> UIBezierPath {
let line = UIBezierPath()
line.move(to: fromPoint)
line.addLine(to: toPoint)
return line
}
func addAnimationLayer() {
createPathOne()
createPathTwo()
createPath()
// set Animation Layer design
animationLayer.fillColor = UIColor.clear.cgColor
animationLayer.strokeColor = UIColor.white.cgColor
animationLayer.lineWidth = 8.0
animationLayer.path = path.cgPath
layer.addSublayer(animationLayer)
expand1()
Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(expand1), userInfo: nil, repeats: true)
}
func expand1() {
let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "position")
expandAnimation.fromValue = [0,0]
expandAnimation.toValue = [-2000,0]
expandAnimation.duration = 10.0
expandAnimation.fillMode = kCAFillModeForwards
expandAnimation.isRemovedOnCompletion = false
animationLayer.add(expandAnimation, forKey: nil)
}
func expand() {
animationLayer.path = centerMainLine.cgPath
if animationDirection{
let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
expandAnimation.fromValue = path1.cgPath
expandAnimation.toValue = path2.cgPath
expandAnimation.duration = animationDuration
expandAnimation.fillMode = kCAFillModeBackwards
expandAnimation.isRemovedOnCompletion = false
animationLayer.add(expandAnimation, forKey: nil)
animationDirection = false
}
else{
let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
expandAnimation.fromValue = path2.cgPath
expandAnimation.toValue = path1.cgPath
expandAnimation.duration = animationDuration
expandAnimation.fillMode = kCAFillModeForwards
expandAnimation.isRemovedOnCompletion = false
animationLayer.add(expandAnimation, forKey: nil)
animationDirection = true
}
}
func createPathOne(){
path1.append(upLeftCircle);
path1.append(centerMainLine);
path1.append(downRightCircle)
}
func createPathTwo(){
path2.append(downLeftCircle);
path2.append(centerMainLine);
path2.append(upRightCircle)
}
func createPath() {
let frameSize = self.frame.size;
let lineHeight1 : CGFloat = 30
let lineHeight2 : CGFloat = 20
let radius1 : CGFloat = 40.0
let radius2 : CGFloat = 20.0
var lastPoint : CGPoint = CGPoint(x:0.0,y:frameSize.height/2 - lineHeight1/2)
for i in 1...10{
let p1 = drawUpCircle(centerPoint: CGPoint(x: lastPoint.x + radius1, y: lastPoint.y ), radiusValue: radius1)
lastPoint = p1.currentPoint;
let p2 = drawLine(fromPoint: lastPoint , toPoint: CGPoint(x:lastPoint.x, y: lastPoint.y+lineHeight1))
lastPoint = p2.currentPoint;
let p3 = drawDownCircle(centerPoint: CGPoint(x:lastPoint.x + radius1, y: lastPoint.y), radiusValue: radius1)
lastPoint = p3.currentPoint;
let p4 = drawLine(fromPoint: lastPoint, toPoint: CGPoint(x:lastPoint.x, y: lastPoint.y - lineHeight2))
lastPoint = p4.currentPoint;
let p5 = drawUpCircle(centerPoint: CGPoint(x:lastPoint.x + radius2, y: lastPoint.y), radiusValue: radius2)
lastPoint = p5.currentPoint;
let p6 = drawLine(fromPoint: lastPoint, toPoint: CGPoint(x:lastPoint.x, y: lastPoint.y + lineHeight2))
lastPoint = p6.currentPoint;
let p7 = drawDownCircle(centerPoint: CGPoint(x:lastPoint.x + radius2, y: lastPoint.y), radiusValue: radius2)
lastPoint = p7.currentPoint
let p8 = drawLine(fromPoint: lastPoint, toPoint: CGPoint(x:lastPoint.x, y: lastPoint.y - lineHeight1))
lastPoint = p8.currentPoint;
path.append(p1)
path.append(p2)
path.append(p3)
path.append(p4)
path.append(p5)
path.append(p6)
path.append(p7)
path.append(p8)
}
}
}
Maintenant, lancez le code pour vérifier le chargeur d'animation. Commenter/ne commenter aucune autre méthode de chargement dans la méthode viewDidAppear de viewcontroller.
Prendre plaisir!!
Si quelqu'un recherche une version Objective C de la solution @rob mayoff
dans SpinnerView.h
#import <UIKit/UIKit.h>
IB_DESIGNABLE
@interface SpinnerView : UIView
@end
dans SpinnerView.m
#import "SpinnerView.h"
#import "Pose.h"
@implementation SpinnerView
- (instancetype) initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
return self;
}
- (instancetype) initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
return self;
}
- (CAShapeLayer*) layer {
return (CAShapeLayer*)super.layer;
}
- (CAShapeLayer*) getLayer{
return (CAShapeLayer*)super.layer;
}
+ (Class)layerClass{
return [CAShapeLayer class];
}
- (void) layoutSubviews{
[super layoutSubviews];
[self getLayer].fillColor = nil;
[self getLayer].strokeColor = [UIColor blackColor].CGColor;
[self getLayer].lineWidth = 3;
[self setPath];
}
- (void) didMoveToWindow{
[self animate];
}
- (void) setPath{
UIBezierPath* bezierPath = ([UIBezierPath bezierPathWithOvalInRect:CGRectInset(self.bounds, [self getLayer].lineWidth/2, [self getLayer].lineWidth/2)]);
[self getLayer].path = bezierPath.CGPath;
}
- (NSArray*) poses{
NSMutableArray* poses = [[NSMutableArray alloc] init];
[poses addObject:[[Pose alloc] initWith:0.0 start:0.000 length:0.7]];
[poses addObject:[[Pose alloc] initWith:0.6 start:0.500 length:0.5]];
[poses addObject:[[Pose alloc] initWith:0.6 start:1.000 length:0.3]];
[poses addObject:[[Pose alloc] initWith:0.6 start:1.500 length:0.1]];
[poses addObject:[[Pose alloc] initWith:0.2 start:1.875 length:0.1]];
[poses addObject:[[Pose alloc] initWith:0.2 start:2.250 length:0.3]];
[poses addObject:[[Pose alloc] initWith:0.2 start:2.625 length:0.7]];
[poses addObject:[[Pose alloc] initWith:0.2 start:3.000 length:0.5]];
return poses;
}
- (void) animate{
CFTimeInterval time = 0;
NSMutableArray* times = [NSMutableArray new];;
CGFloat start = 0;
NSMutableArray* rotations = [NSMutableArray new];
NSMutableArray* strokeEnds = [NSMutableArray new];
NSArray* posses = [self poses];
double totalSeconds = [[posses valueForKeyPath:@"@sum.secondsSincePriorPose"] doubleValue];
for(Pose* pose in posses){
time += pose.secondsSincePriorPose;
[times addObject:[NSNumber numberWithDouble:time/totalSeconds]];
start = pose.start;
[rotations addObject:[NSNumber numberWithDouble:start*2*M_PI]];
[strokeEnds addObject:[NSNumber numberWithDouble:pose.length]];
}
[times addObject:[times lastObject]];
[rotations addObject:[rotations firstObject]];
[strokeEnds addObject:[strokeEnds firstObject]];
[self animateKeyPath:@"strokeEnd" duration:totalSeconds times:times values:strokeEnds];
[self animateKeyPath:@"transform.rotation" duration:totalSeconds times:times values:rotations];
[self animateStrokeHueWithDuration:totalSeconds * 5];
}
- (void) animateKeyPath:(NSString*)keyPath duration:(CFTimeInterval)duration times:(NSArray*)times values:(NSArray*)values{
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:keyPath];
animation.keyTimes = times;
animation.values = values;
animation.calculationMode = kCAAnimationLinear;
animation.duration = duration;
animation.repeatCount = FLT_MAX;
[[self getLayer] addAnimation:animation forKey:animation.keyPath];
}
- (void) animateStrokeHueWithDuration:(CFTimeInterval)duration{
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"strokeColor"];
NSMutableArray *keyTimes = [NSMutableArray array];
NSMutableArray *values = [NSMutableArray array];
for (NSInteger i = 0; i < 36; i++) {
[keyTimes addObject: [NSNumber numberWithDouble:(CFTimeInterval)i/(CFTimeInterval)36]];
[values addObject:(id)[UIColor colorWithHue:(CGFloat)i/(CGFloat)36 saturation:1 brightness:1 alpha:1].CGColor];
}
animation.keyTimes = keyTimes;
animation.values = values;
animation.calculationMode = kCAAnimationLinear;
animation.duration = duration;
animation.repeatCount = FLT_MAX;
[[self getLayer] addAnimation:animation forKey:animation.keyPath];
}
@end
Pose.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface Pose : NSObject
@property CFTimeInterval secondsSincePriorPose;
@property CGFloat start;
@property CGFloat length;
- (instancetype) initWith:(CFTimeInterval)timeInterval start:(CGFloat)start length:(CGFloat)length;
@end
Pose.m
#import "Pose.h"
#import <UIKit/UIKit.h>
@implementation Pose
- (instancetype) initWith:(CFTimeInterval)timeInterval start:(CGFloat)start length:(CGFloat)length{
self = [super init];
self.start = start;
self.length = length;
self.secondsSincePriorPose = timeInterval;
return self;
}
@end