iOS Core Animation Queue - ios

I have multiple CALayers. And I want to be able to animate them sequentally using CAAnimation (and it's subclasses) animations. I wrote methods that prepare animations and add it to layer, one animation per method. Now, how I could create animaiton queue? CAAnimationGroup couldn't be used because animations are applied to different CALayers. NSOperationQueue isn't working because all UI actions should be done on main thread. The only solution I found is to create NSArray of NSInvocation objects, but this solution doesn't seem to work in iOS 5...

CAAnimationGroup couldn't be used because animations are applied to different CALayers
Actually, that's not true. A single CAAnimationGroup can animate different CALayers. You add the animation to the superlayer and refer to properties of the different layers using the key path notation "sublayers.[layername].[property]".

Use a common delegate for all your animations. As each animation ends, it will notify its delegate (-animationDidStop:finished:), which can then fire up the next one.

Related

Observe progress of UIView.animateWithDuration/CABasicAnimation?

Is there a way to observe the "time progress" of UIView.animateWithDuration...family of methods from UIView /alternatively CA animations?
I am animating a view's frame and I need to be informed how it is progressing.
My line of thinking was I can either
1) tap into CAAnimation related stuff or
2) observe the animated properties (like frame) and do my own calculations each screen frame.
Approach 1) turns out to be a dead end, inspecting the internal of how CAAnimations work told me absolutely nothing...and 2) is flawed as the "model layer tree is updated immediately and tapping into the presentation tree is difficult as the presentation layer is nil when you start.
I am pretty desperate, I was thinking that hooking into CADisplayLink will give me a tick and then I simply check something that is affected by the animation but there is nothing to tap to.
Do you think going the NSTimer way that is launched in the same scope as the animation method is ok? If I know animation duration then I can generate the progress myself.
If all you want is the time value, then you can do math on the CACurrentMediaTime() minus the animation start time. I have a sample project on Github called KeyframeViewAnimations that does exactly that.
That project supports pausing and resuming and scrubbing both UIView and CAAnimation based animations. In both cases it digs into the underlying CAAnimations.
I have another project that uses the values of the animated layer's presentationLayer in order to do hit testing so you can tap on an in-flight view and start/pause the animation. That one can be found here:
iOS-CAAnimation-group-demo
My code uses an NSTimer to update the progress of the animation. It would be better to use a CADisplayLink timer, as you mentioned.
I am also looking at the new UIViewPropertyAnimator class that was added to iOS 10. That makes pausing, reversing, and scrubbing UIView animations easy without having to dig into the underlying CAAnimations. See this thread I just posted:
Is there a way to observe changes to fractionComplete in UIViewPropertyAnimator

How can I start/stop a DisplayLink when a CALayer is on/offscreen?

I'm making an animation in a CALayer that needs to update smoothly over time. I tried the following approaches:
CADisplayLink for iOS, CVDisplayLink for OS X, plus calling setNeedsDisplay from the callback
CABasicAnimation for a custom displayTime property, with fromValue=0, toValue=1, duration=1, repeatCount=HUGE_VALF, cumulative=YES, and needsDisplayForKey:#"displayTime" returning YES.
These both work to draw the animation, but I'm not sure which is better. Does Core Animation internally use a display link for updating animations?
Furthermore, neither of these allow me to know when the layer comes onscreen or is offscreen. Even the animation continues to run when the NSWindow is closed (-drawInContext: is continuously called). The kCAOnOrderOut action is not triggered, nor is -setHidden: called. How can the layer tell when it is on or offscreen?

Why do docs indicate CALayer animations must be in UIView animation blocks?

I am currently reading Apple's Core Animation Guide, where I found the following passage regarding layer-backed views in iOS:
If you want to use Core Animation classes to initiate animations, you must issue all of your Core Animation calls from inside a view-based animation block. The UIView class disables layer animations by default but reenables them inside animation blocks. So any changes you make outside of an animation block are not animated.
Just below the quote, the documentation includes the following code listing:
[UIView animateWithDuration:1.0 animations:^{
// Change the opacity implicitly.
myView.layer.opacity = 0.0;
// Change the position explicitly.
CABasicAnimation* theAnim = [CABasicAnimation animationWithKeyPath:#"position"];
theAnim.fromValue = [NSValue valueWithCGPoint:myView.layer.position];
theAnim.toValue = [NSValue valueWithCGPoint:myNewPosition];
theAnim.duration = 3.0;
[myView.layer addAnimation:theAnim forKey:#"AnimateFrame"];
}];
which implies that both implicit and explicit animations on CALayers backing UIViews must be placed within an animation block.
However, I have found this to be patently untrue. Specifically, I have successfully implemented explicit animations using Core Animation classes outside of a UIView animation block.
Have I misunderstood this passage, or is it out-of-date, or something else?
Some additional notes:
I assume that "the UIView class disables layer animations by default but reenables them inside animation blocks" refers to the +[UIView setAnimationsEnabled:] method. When I get back to a computer that can do so, I'll check to see whether +[UIView areAnimationsEnabled] returns YES or NO.
That quote refers to the layer that is backing the view. It is not true for stand-alone layers that you create and manage yourself.
Every view on iOS is backed by a layer. When you change the view's properties, it changes the underlying layer property. By default the layer would have implicit animations, but the layer "disables" that behavior except for when you are inside of an UIView animation block. This is what that part of the documentation is referring to.
There are a couple of ways you can use Core Animation to animate a layer property. The most common is to add the animation object to the layer when you want to animate the property, but you can also make customizations through the actions dictionary and the styles dictionary if you always want to animate when a property changes. The last two would also be disabled for the layer that is backing a view.

Multiple CADisplayLink's or as few as possible?

Example
I have a UIView which simply draws a custom spinner using CoreGraphics. It is updating itself using CADisplayLink so it can draw each frame.
Another UIView subclass draws a progress-bar also using CoreGraphics. It is also updating itself using CADisplayLink.
The list could potentially go on. And as you can see codewise it is obviously easiest that each component has its own CADisplayLink.
Is it better or worse to have many CADisplayLinks? (A workaround to having multiple CADisplayLink instances would be to have one instance which sends to many delegates/blocks each callback)

When is the right time to animate and add sublayers in an UIView subclass

If I create a subclassed UIView and want to use a bunch of custom made content (animations via CABasicAnimation using CAShapeLayers, custom made drawings using CGContextRef in drawRect) when is the right time to create, add and animate sublayers?
I know that I should perform custom drawings in the drawRect method (and since there is the only place when I can actually get UIGraphicsGetCurrentContext() that kinda narrows my choice down). Now I have been creating sublayers, animations and all that other non-drawing related stuff in the drawRect as well. But I'm not sure that drawRect is the best place to do those kind of activities.
I am familiar with layoutSubviews method and some other UIView methods but haven't actually implemented any of them except drawRect.
So if I repeat myself one more time - the question goes: Where to add sublayers, where to animate them and are there any tricks or catches I should be aware of?
You can create and add the layers at init, if they are permanent. If they are dynamic, layoutSubviews is better. This obviously means you need to setNeedsLayout whenever a new layer/item is required. You can mix and match as much as you want between -init and -layoutSubviews, but if I had to pick, I'd say lean toward using layoutSubviews. Don't use drawRect.
You can set properties (strokeWidth, lineColor, path, etc) to a CAShapeLayer at either the time of creation or during normal execution. This includes setting a path to CAShapeLayer. Again, don't set properties in drawRect.
If you want to do custom drawing on the layer you can subclass a layer and use drawRect on that layer. This can be good if the CALAyers need to be reused and extended. You can also supply a CALayerDelegate, as long as its not the UIView. See these questions: Using CALayer Delegate AND
iOS: Using UIView's 'drawRect:' vs. its layer's delagate 'drawLayer:inContext:'
Animation is easy, and it follows the same principles as creating and setting properties. Make sure you understand when automatic animations will be invoked and how to disable them: Disabling implicit animations in -[CALayer setNeedsDisplayInRect:]
Again don't try to animate from drawRect: How to make my UIBezierPath animated with CAShapeLayer?
Drawing is expensive. Animation is cheap. Using drawRect too much will cause a big performance hit to your applications. Use drawRect/setNeedsDisplay sparingly, for instance on a state change (selected/unselected). Whenever possible modify views with animations or top level properties, and don't redraw the view. Making drawRect do anything OTHER than draw could result in you calling setNeedsDisplay unnecessarily. An easy upfront optimization is to call drawRect as little as possible, or not at all.

Resources