Reputation: 1276
I've added a few layers to an UIView and now wish to animate these in sequence.
I have 5 layers to animate, these are all circles. I use observable fields to get notified of the changes in the values and start to animate using the needsDisplayForKey() and actionForKey() methods.
The observable fields:
@NSManaged private var _startAngle: CGFloat
@NSManaged private var _endAngle: CGFloat
Currently all the layers animate at the same time when I change one of the observable fields for each layer. I wish to change this and wish to animate all of them in sequence in a time span of 2.5 seconds. So when one animation has finished, I wish to animate the next layer etc.
For now the only way I can know that an animation has finished is by using the animationDidStop() method. However this way I have no way of knowing of the other layers that are animating. Do you guys know what a good solution would be for this?
My layer:
class HourLayer: CAShapeLayer {
@NSManaged private var _startAngle: CGFloat
@NSManaged private var _endAngle: CGFloat
@NSManaged private var _fillColor: UIColor
@NSManaged private var _strokeWidth: CGFloat
@NSManaged private var _strokeColor: UIColor
@NSManaged private var _duration: CFTimeInterval
private var _previousEndAngle: CGFloat = 0.0
private var _previousStartAngle: CGFloat = 0.0
private let _lenghtOfArc: CGFloat = CGFloat(2 * M_PI)
internal var StartAngle: CGFloat {
get {
return _startAngle
}
set {
_startAngle = newValue
}
}
internal var EndAngle: CGFloat {
get {
return _endAngle
}
set {
_endAngle = newValue
}
}
internal var FillColor: UIColor {
get {
return _fillColor
}
set {
_fillColor = newValue
}
}
internal var StrokeWidth: CGFloat {
get {
return _strokeWidth
}
set {
_strokeWidth = newValue
}
}
internal var StrokeColor: UIColor {
get {
return _strokeColor
}
set {
_strokeColor = newValue
}
}
internal var DurationOfAnimation: CFTimeInterval {
get {
return _duration
}
set {
_duration = newValue
}
}
required override init!() {
super.init()
self._startAngle = 0.0
self._endAngle = 0.0
self._fillColor = UIColor.clearColor()
self._strokeWidth = 4.0
self._strokeColor = UIColor.blackColor()
self._duration = 1.0
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
fatalError("required init(:coder) is missing")
}
override init!(layer: AnyObject!) {
super.init(layer: layer)
if layer.isKindOfClass(HourLayer) {
var other: HourLayer = layer as HourLayer
self._startAngle = other._startAngle
self._endAngle = other._endAngle
self._fillColor = other._fillColor
self._strokeWidth = other._strokeWidth
self._strokeColor = other._strokeColor
self._duration = other._duration
}
}
override var frame: CGRect {
didSet {
self.setNeedsLayout()
self.setNeedsDisplay()
}
}
override func actionForKey(event: String!) -> CAAction! {
if event == "_startAngle" || event == "_endAngle" {
return makeAnimationForKey(event)
}
return super.actionForKey(event)
}
private func makeAnimationForKey(event: String) -> CABasicAnimation {
// We want to animate the strokeEnd property of the circleLayer
let animation: CABasicAnimation = CABasicAnimation(keyPath: event)
// Set the animation duration appropriately
animation.duration = self._duration
// When no animation has been triggered and the presentation layer does not exist yet.
if self.presentationLayer() == nil {
if event == "_startAngle" {
animation.fromValue = self._previousStartAngle
self._previousStartAngle = self._startAngle
} else if event == "_endAngle" {
animation.fromValue = self._previousEndAngle
self._previousEndAngle = self._endAngle
}
} else {
animation.fromValue = self.presentationLayer()!.valueForKey(event)
}
animation.removedOnCompletion = false
animation.delegate = self
// Do a linear animation (i.e. the speed of the animation stays the same)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
return animation
}
override class func needsDisplayForKey(key: String) -> Bool {
if key == "_startAngle" || key == "_endAngle" {
return true
}
return super.needsDisplayForKey(key)
}
override func drawInContext(ctx: CGContext!) {
var center: CGPoint = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2)
var radius: CGFloat = CGFloat((CGFloat(self.bounds.width) - CGFloat(_strokeWidth)) / 2)
// Set the stroke color
CGContextSetStrokeColorWithColor(ctx, _strokeColor.CGColor)
// Set the line width
CGContextSetLineWidth(ctx, _strokeWidth)
// Set the fill color (if you are filling the circle)
CGContextSetFillColorWithColor(ctx, UIColor.clearColor().CGColor)
// Draw the arc around the circle
CGContextAddArc(ctx, center.x, center.y, radius, _startAngle, _lenghtOfArc * _endAngle, 0)
// Draw the arc
CGContextDrawPath(ctx, kCGPathStroke) // or kCGPathFillStroke to fill and stroke the circle
super.drawInContext(ctx)
}
override func animationDidStop(anim: CAAnimation!, finished flag: Bool) {
// Trigger animation for other layer by setting their values for _startAngle and _endAngle.
}
}
Upvotes: 0
Views: 1952
Reputation: 1276
The Apple documentation states the following: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreAnimation_guide/CreatingBasicAnimations/CreatingBasicAnimations.html
If you want to chain two animations together so that one starts when the other finishes, do not use animation notifications. Instead, use the beginTime property of your animation objects to start each one at the desired time. To chain two animations together, set the start time of the second animation to the end time of the first animation. For more information about animation and timing values, see Customizing the Timing of an Animation.
I have got some stuff working, but I do not find it the most ideal solution. I would rather think the notifications are a nicer way, but hey if Apple says so right....
Also I did have to set: animation.fillMode = kCAFillModeBackwards Otherwise the model layer would make the circles reach their final state before animating and only later would the animation trigger.
Edit
Ok so I found that using multiple layers for my animations is slow (I used multiple layers for each view, so a circle I wanted to animate and I had about six of 'em. Do that times 8 is very slow (6x8=48 animations).). It is just way faster to animate everything in one layer.
Upvotes: 1
Reputation: 89
try this..
UIview.animateWithDuration(2.0, delay 2.0, options: .CurveEaseIn, animations: {
//enter item to be animated here
}, completion: nil)
Upvotes: 0
Reputation: 2417
My solution is in objective-c, you need to adopt this for swift. Give it a try: -
Create an NSOperationQueue and add the animation calls to this queue. Set the setMaxConcurrentOperationCount = 1
to ensure the sequential execution.
NSOperationQueue *animQueue = [[NSOperationQueue alloc] init];
[animQueue setMaxConcurrentOperationCount:1];
Now from function actionForKey
instead of calling the animation function, add it to the operation queue
NSBlockOperation *animOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakOp = animOperation;
[weakOp addExecutionBlock:^{
[self makeAnimationForKey:event)]
}];
[animQueue addOperation:animOperation];
I have not tested this, but i think it should work.
Upvotes: 0