iOS: Should I Add UIViews or CALayers for animation? - ios

Let's say I want to add 50 images to a view for the purpose of animating them. And let's suppose I'm planning on using Core Animation (e.g., CABasicAnimation) rather than "UIView" animation.
Am I better off implementing this by adding 50 subviews or 50 sublayers? Does it make a difference?
Thanks.

As I describe here, I've used both UIViews and CALayers in animations and found a negligible performance difference between them. UIViews are very lightweight wrappers around the layers. Also, any layer-based animations you need can be applied to a UIView's backing layer easily.
I've used CALayers directly in situations where I wanted to create cross-platform (Mac / iOS) UI elements, because CALayers are almost identical in their implementation on both platforms (unlike the significantly different NSViews and UIViews). CALayers don't have any touch-handling routines out of the box, but you can add that capability if you need to.
There are also some edge cases where you might want to work directly with layers, like when trying to do limited 3-D manipulation of the layers (as in a CoverFlow effect) or when using a CAReplicatorLayer to produce particle effects.

UIViews contain sublayers, so they are heavier weight, and contain stuff you probably don't need for all 50 images, such as event and touch handlers/variables. So using layers would probably be slightly more efficient and use a bit less memory than using views for each image.

The difference for such a small number of images is negligible. Use what's most convenient.

I've not done animation (yet :-), but the stuff I remember reading about it suggests to create one image with all 50 tiled on it and then just offset to the correct image when drawing. That way you only need one layer or UIImage or whatever to display it. I don't know about speed, but I'd guess it would save memory and would probably be easier to manage and code.

Related

Usage UIView vs. CAShapeLayer (drawing, animating, user interaction)

I have a pretty basic question: How do you choose between using UIView and CAShapeLayer when you want to draw shapes (I'm not talking about textfields, switches or other controls, just drawing)?
My understanding is that UIView (as part of UIKit) uses a normal CALayer under the hood to draw its content. If this is correct, then CAShapeLayer (or CALayer in general) would be the exact same thing, only without the extras UIKit gives you.
Then, when does using a UIView make sense, and when does using CAShapeLayer make sense?
Is CAShapeLayer faster? Is UIKit more optimised for gesture recognition or user interaction in general?
To give you more context, here's what I was trying to do when this question came up:
I want these red circles to rotate around the center circle. However, the user should be able to tap on the red circles while they are rotating.
Here, I see two main options (there may be more) to add one of those red circles:
Create a UIView, manipulate its layer's cornerRadius and rotate it using CGAffineTransform.
Create a CAShapeLayer with a UIBezierPath. I could rotate it using CATransform3D
The only problem I have here is the user interaction. As it's constantly moving (rotating), I'd have to access the correct frame. I can do that using the presentation layer (which I think UIView is also using under the hood).
At this point, I'm not sure whether to use UIViews or CAShapeLayers. Also, I'm not sure if animating it this way is the correct way in this case. There may be better options that will also erase the question about which one to use.
Thanks for your thoughts about this.
Quoting from Apple's doc
Layers are not a replacement for your app’s views—that is, you cannot
create a visual interface based solely on layer objects. Layers
provide infrastructure for your views. Specifically, layers make it
easier and more efficient to draw and animate the contents of views
and maintain high frame rates while doing so. However, there are many
things that layers do not do. Layers do not handle events, draw
content, participate in the responder chain, or do many other things.
For this reason, every app must still have one or more views to handle
those kinds of interactions.
In iOS, every view is backed by a corresponding layer object but in OS
X you must decide which views should have layers. In OS X v10.8 and
later, it probably makes sense to add layers to all of your views.
However, you are not required to do so and can still disable layers in
cases where the overhead is unwarranted and unneeded. Layers do
increase your app’s memory overhead somewhat but their benefits often
outweigh the disadvantage, so it is always best to test the
performance of your app before disabling layer support.
When you enable layer support for a view, you create what is referred
to as a layer-backed view. In a layer-backed view, the system is
responsible for creating the underlying layer object and for keeping
that layer in sync with the view. All iOS views are layer-backed and
most views in OS X are as well. However, in OS X, you can also create
a layer-hosting view, which is a view where you supply the layer
object yourself. For a layer-hosting view, AppKit takes a hands off
approach with managing the layer and does not modify it in response to
view changes.
Read : https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreAnimation_guide/CoreAnimationBasics/CoreAnimationBasics.html#//apple_ref/doc/uid/TP40004514-CH2-SW3
Now time for Some QNA
Question 1:
My understanding is that UIView (as part of UIKit) uses a normal
CALayer under the hood to draw its content. If this is correct, then
CAShapeLayer (or CALayer in general) would be the exact same thing,
only without the extras UIKit gives you
Again Quoting Apple doc
Layer-backed views create an instance of the CALayer class by default,
and in most cases you might not need a different type of layer object.
However, Core Animation provides different layer classes, each of
which provides specialized capabilities that you might find useful.
Choosing a different layer class might enable you to improve
performance or support a specific type of content in a simple way
Clearly when you try to draw various shapes using CAShapeLayer is beneficial in terms of performance when compared to CALayer.
Question 2
Is CAShapeLayer faster? Is UIKit more optimised for gesture
recognition or user interaction in general?
Clearly Layers do not handle events, draw content, participate in the responder chain, or do many other things. So none of the layer can recognize user interactions no matter whether its CALayer or CAShapeLayer
Question 3
At this point, I'm not sure whether to use UIViews or CAShapeLayers
As you have specified in your question you want user interactions of red circles and because we are now aware of the fact that CAShapeLayer/CALayer will not respond to user interaction its pretty clear that you have to use UIView rather than layer.

For performance, is it better to hide or remove CALayers on iOS?

This is related to the following question: How to improve performance of CALayer animations?
I currently have a view that can have several hundred CALayers. I know having many CALayers will cause performance degradation limited by the processor, but I wanted to see if someone out there has experimented with several of the following methods and can provide guidance.
To better give you an idea of what I'm trying to do, imagine that I have a dot drawn on my layer. When I zoom in, I want the dot to become a star. When I zoom out, I want the star to go back to being a dot. Now imagine that I have hundreds of these dots.
Redrawing single layer vs Multiple pre-drawn layers
Instead of having 1 single layer where I redraw when the zoom changes, I've been experimenting with having 2 separate layers with it pre-drawn and when the zoom changes, I just hide the one and show the other.
[layer configureWithZoom:zoom];
[layer setNeedsDisplay];
vs
layer1.hidden = (zoom == DEFAULT_ZOOM);
layer2.hidden = (zoom != DEFAULT_ZOOM);
I understand that having a bunch of layers will increase my memory footprint and if I had 50 layers before, I now have 100 layers. But in terms of performance, will this help?
Hiding vs Removing
Given the above, I now have layers that aren't needed at all times. Is it better to hide those layers or remove them?
Setting layer.hidden = YES vs [layer removeFromSuperlayer] when I want it to go away.
Setting layer.hidden = NO vs [superlayer addSublayer:layer] when I want it to come back.
If I hide a layer, but animate the superlayer, is there a performance hit during animations because a sublayer is hidden instead of removed? How does that performance hit compare to adding/removing the sublayers?
Parent layer vs Direct layering
One of the problems with having separate layers is now that I need code to manage them as a unit. Previously changing the layer's position was as simple as layer.position = newCoordinates;. Now I would need to do:
layer1.position = newCoordinates1;
layer2.position = newCoordinates2;
I decided to simplify this and create a parent layer and add both layer1 and layer2 to it. My main layer can now just manipulate the parent layer instead of the individual layers. My parent layer would also handle the logic of which of the 2 layers to show or hide.
However, this now introduces a 3rd layer (at the start we had 1, now we have 3). I'm curious if a layer that has no actual drawing besides its sublayers has any impact on performance. Basically does an empty layer having 2 sublayers have a performance hit compared to having those 2 sublayers added directly to the superlayer.
Any guidance would be appreciated.
Redrawing single layer vs Multiple pre-drawn layers
It depends on how you draw here. I assume you're using drawInContext of a CALayer with some custom code? If yes, it will definitely help if the call of this drawing code is not necessary every time you change your zoom level.
The memory footprint from having double the layers as before really shouldn't hurt you here.
Hiding vs Removing
I'm working with a similar setup to yours for quite a time now and have hundreds (thousands) of CALayers at the same time on the screen. From my experiments I found out that there is a huge performance hit coming from hidden layers. For me it was always best to completely remove and re-add them later.
This is really really frustrating as it's nowhere said in the docs and usually you'd never come to the idea that a hidden layer costs (a lot of) performance.
Parent layer vs Direct layering
About your third question: I'm not 100 percent sure if your empty layer that functions as a parent for your two layers is a performance hit, but I would guess that it is (from my experience every extra layer that CA has to handle causes it to run slower).
The question is: Are you able to do it without a layer in between? Is there a certain order your layers have to be in?
You could write a custom class, subclassing NSObject, that takes your two layers, and that manages everything about them (changing position, removing and adding from superlayer etc). This way you would have an easy interface but no extra layer on screen.
If that is not an option, you could try to use CATransformLayer as a compositiing layer for your two layers. This layer doesn't do anything but be a layer for compositing (and it can be used for 3D). Maybe this one takes less performance than a "normal" CALayer in your scenario.
General performance adivce
If possible, do not use drawInContext to draw your layers. If you can, use CAShapeLayer for your stars, it is much faster and has nice anti-aliasing.
Keep a look at the CPU-meter built into Xcode (not the profiler). There you see the CPU-usage of "other processes". In CA-Apps which need a lot of performance, this "other processes" section is basically the CA-RenderServer (backboardd). Here you can see how much CPU your drawing costs right now.
Otherwise if you're concerned about your memory usage, check out the allocations profiler (this time the real profiler) and check how much memory the different versions of your code (50 vs. 100 layers) take.

Is using CAShapeLayer backed UIView's a better approach than using plain CAShapeLayer?

I am working on a prototype for building a DFD builder. This will have a palette of different objects for drawing DFD. (Rectangle boxes, arrows, Ellipses,circles).
I initially planned to use plain CAShapeLayer's to create objects and add it to the superview's layer (with help of CGPaths or UIBezierPath), so that I will reduce the memory footprint of the application.
Then I realized that using CAShapeLayer and adding them directly in super view's layer makes me to handle touches and use hit test to find the CAShapeLayer that was interacted by the user.
So I am planning to create a Custom UIView backed by CAShapeLayer so that I do not bother about handling the touches and spotting the specific object interacted by user.
Is this approach better than using plain CAShapeLayer? and if yes, please provide the reasons?
UPDATE:
#matt I did a memory testing and the results show that using CAShapeLayer will save memory
Neither approach is "better". A layer cannot exist without a view, and you can hit-test a layer just as well as you can hit-test a view. If you have multiple tappable drawn objects then it is going to be simplest if they are all separate layers so that you don't have to calculate which one was tapped; but that does not mean that they all need to be separate views.
As for "memory footprint", you are optimizing prematurely, a cardinal sin. Have you actual evidence that 100 layers (let's say) takes up less "memory footprint" than 100 views? Unless you do, you can't use that as a decision criterion.

The relationship between CoreGraphics, UIViews and CALayers

I always used CoreGraphics and CoreAnimation, I understand how each of them works on their own, but not those edge cases when one have to talk with the other. I also understand that UIViews are a nice wrapper for CALayer, where CALayer does all the heavy lifting of rendering, and the UIView adds the touch-based responsiveness.
But, all the questions I have seen thus far, attack the problem from one side or the other, not the interplay between them, specially between CoreGraphics and CALayer.
Anyway, my question is ...
How does CoreGraphics relate to CALayer?
My understanding is that a CALayer wraps the CoreGraphics methods to draw itself, but does it once, and can live with the snapshot of itself until invalidated. But, how these drawing methods interplay with the sublayers of that layer? Are they exclusive?
For example, what happens when I have a UIView that has sub-views, and I overload the drawRect method? How does that affect the drawing of its sublayers?
Is it even a good idea to intermix the two inside the same function?
Also, I'm asking only about iOS, I understand that Mac is a different beast (and also have those fancy CIFilters, bastards!).
Prior Research
Here's some related questions I've researched beforehand:
confusion regarding quartz2d, core graphics, core animation, core images. This question asks the differences between each other, and the chosen answer indeed delivers, but it answers for each individual library as if the other didn't exist.
To Drawrect or not to Drawrect. Another great question, but it addresses only the subject of drawing CoreGraphics vs handing the problem to UIKit, but anyway, the chosen answer delivers parts of the puzzle.
Animating Pie Slices with Custom CALayer. Must be one of the most valuable tutorials I've seen in this subject, it's the only one that has guided me through to drawing a CALayer
What is different between CoreGraphics and CoreAnimation Absolutely disappointed on how quick the asker accepted the answer, I feel that there's a whole lot more going in here.
Various WWDC videos, but I haven't seen one that explains in detail the general scope. If anyone replies with a WWDC video that does, I'll consider that a valid answer.
I'll try to answer your question at a conceptual, 20,000ft level. I will try to disclaim my points where I'm over-generalizing, but I'll attempt to hit the common case.
Perhaps the easiest way to think about it is this: In the GPU's memory you have textures which, for the purposes of this discussion, are bitmap images. A CALayer might have a texture associated with it, or it might not. These cases would correspond to a layer with a -drawRect: method, and a layer that exists solely to contain sublayers, respectively. Conceptually, each layer that has a texture associated with it has a different texture all it's own (there are some details and optimizations that make this not strictly/universally true, but in the general, abstract case, it can help to think of it this way.) With that in mind, a superlayer's -drawRect: method has no effect on any of its sublayers' -drawRect: methods, and (again, in the general case) a sublayer's -drawRect: method has no effect on its superlayer's -drawRect: method. Each draws into its own texture (also called a "backing store") and then, based on the layer tree and the associated geometries and transforms, the GPU composites all these textures together into what you see on the screen. When one of the layers is invalidated, directly or indirectly (via -setNeedsDisplayInRect:), then when CA goes to display the next frame on screen, the invalid layers will be redrawn by virtue of having their -drawRect: methods called. That will update the associated texture, and once all the invalid layers' textures are updated, the GPU will composite them, generating the final bitmap that you see on-screen.
So to answer your first question: In the general case, no, there is no interplay between the -drawRect: methods of distinct CALayers.
As to your second question: For the purposes of this discussion you can think of UIViews as being the same as CALayers. The interrelationship with respect to drawing and textures is largely unchanged from that of non-UIView CALayers.
To your third question: Is it a good idea to intermix UIViews and CALayers? Every UIView has a CALayer backing it (all views in UIKit are layer-backed, which is not normally the case on OSX.) So at some level, they're "intermixed" whether you want them to be or not. It is perfectly fine to add CALayer sublayers to the layer that backs a UIView, although that layer will not have all the added functionality that UIView brings to the party. If that layer's purpose is just to generate an image for display, then that's fine. If you want the sub-layer/view to be a first class participant in touch handling, or to be positioned/sized using AutoLayout, then it will need to be a UIView. It's probably best to think of a UIView as a CALayer with a bunch of extra functionality added to it. If you need that functionality, use a UIView. If you don't, use a CALayer.
In sum: CoreGraphics is an API for drawing 2D graphics. One common use of CG (but not the only one) is to draw bitmap images. CoreAnimation is an API providing an organized framework for manipulating bitmaps on-screen. You could meaningfully use CoreAnimation without ever calling a CoreGraphics drawing primitive, for example, if all your textures were backed by images that were compiled into your application at build time.
Hopefully this is helpful. Leave comments if you need clarification, and I'll edit to oblige.

UIView animation vs CALayers

I'm struggling with conceptualizing animations with a CALayer as opposed to UIView's own animation methods. Throw "Core Animation" into this and, well, maybe someone can articulate these concepts from a high level so I can better visualize what's happening and why I'd want to migrate UIView animations (which I'm quite familiar with now) to CALayer animations on the iPhone. Every view in Cocoa-Touch automatically gets a layer. And, it seems, you can animate one and/or the other?!? Even mix them together?!? But why? Where's the line? What's the pro/con to each?
The Core Animation Programming Guide immediately jumps into Layer & Timing Classes and I think need to take a step back and understand why these varied pieces exist and how relate to each other.
Use views for control and layers for eye candy. Layers don't receive events so it's easier to use a view for those cases, but when you want to animate a sprite or backgrounds, etc., layers make sense. Events pass right through layers to the backing view so you can have a pretty visual representation without messing up your events. Try to overlay a view that you're just using for visual representation and you'll have to pass tap events through to the underlying view yourself.
An UIView is always rendered to a CALayer. When you use UIView methods to animate a view, you are effectively manipulating the underlying CALayer.
If you need to do simple things, use the UIView methods. For more complex situatins, or if you want layers not associated with any view in particular, use CALayers.
I've done a bunch of apps in the past year. Here's my rule of thumb:
Use UIView until it doesn't do what you want.
Then move to CoreAnimation. But before you get into it too much...
If you write more than a few animations, use Cocos2D.
UIView transforms are only 2D and are restricted to that, LAyer transforms however can be 3D and you should use those if you want to do 3D stuff, UIView animation will work if you change either the UIView transform or the CALayer transform. So at a basic level, you can do a lot more manipulation when you are working with a Layer rather than the View.
I am not sure if I am misunderstanding Chris' response to "What's Cocos2D doing better? Don't you have other problems then, regarding the touch event handling and many other stuff that misses in openGL ES?"
It sounds like the answer suggests Cocos2D is not based on the OpenGL ES framework when in fact it actual is. While it is a great 2D game engine it does implement OpenGL for much of it's rendering - attached to a physics library it allows for a lot of very interesting possibilities for animation - and Chris is correct - it is a lot less coding indeed.

Resources