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.
Related
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.
As far as I know we can use core graphic such as CGContextSetShadowWithColor to draw a shadow. However, we can also use CALayer to show the shadow as well.
Question :
what are the differences between 2 of them. Are there any rules to determine when we use core graphic to draw a or when we use CALayer to do the job
I would have to say that using CoreAnimation is always preferred over CoreGraphics, since it's more high level, and abstracts the low-level details of drawing the shadow. (It may also allow apple to optimize the shadow drawing without hurting your code syntax).
However, there are times where you are overriding drawRect: anyways, and you have very specific use for the shadow, not the whole view's layer. You might wanna use CoreGraphics shadows here.
One last note, CoreAnimation gradients are much faster when rendering, take my word for it. I used it on UITableViewCell, and the scroll performance significantly increased, as opposed to using CoreGraphics Gradients. That comes at a price, though. It's a bit worse-looking.
I'm subclassing CALayer to provide my own drawing in method. For optimization I call -[MyLayer setNeedsDisplayInRect:] instead of -[MyLayer setNeedsDisplay]. In the drawing method I get the rect which should be redrawn via CGContextGetClipBoundingBox().
If I use this layer as the base layer of an UIView every thing works as expected. The problem arises, as soon as I use my custom layer as a sublayer of an other CALayer. Than CGContextGetClipBoundingBox() always returns the rect of the bounds of that layer.
Any ideas?
[EDIT]
It seems, that there is no guaranty, that the content of the CALayer is cached and only the dirty part gets redrawn. I did a small test and stored the rect that needs display as a separate property. The result was, that only this part was visible on the screen.
I'll now render to an image context and keep that image as a cache. In the draw method I'll only display the cached image.
Apple's documentation is unfortunately conflicting as the docs on -setNeedsDisplayInRect do not indicate whether the method works in practice. Based on my own experience, this technote sets it straight:
Note that, because of the way that iPhone/iPod touch/iPad updates its screen, the entire view will be redrawn if you call -setNeedsDisplayInRect: or -setNeedsDisplay:.
That being said, there are a number of things you can look into if you think that you are hitting a wall due to redundant drawing.
If drawing images, the biggest performance improvement you can make is to use images of the same dimensions at which you draw. If they're not, try to cache your image by rendering it to some offscreen bitmap context and bring it back later on.
Check out the shouldRasterize property on CALayer. This can be a godsend if you are trying to manipulate a layer whose sublayers potentially constitute a complex layer hierarchy. Be sure to check out how you're doing in Instruments by ticking the Color Hits Green and Misses Red box in the Core Animation instrument. If you see a lot of red, chances are using shouldRasterize is hurting more than it's helping.
Even better than shouldRasterize is to flatten your layer hierarchy, as then you can avoid the extra overhead that shouldRasterize incurs when flattening your layer hierarchy real time. Of course this is not always possible, but don't be afraid to try :)
If you're drawing images, try experimenting with your blending mode. If you happen to be drawing opaque images, there's no need to be using normal source over methods (which use both read/write bandwidth). Try kCGBlendModeCopy, which allows you to eliminate read bandwidth overhead.
Check out CGLayerRef, which allows you to cache Core Graphics output across various calls to your drawing methods. My experience is that, unless you're doing some hardcore pixel pushing, that this ends up being more costly than just redrawing. See this for an interesting read.
Above all, Instruments is your friend. Check out a couple videos from past WWDCs (2012, 2011, and 2010); they all have great info about how to fine-tune performance.
Please feel free to ask any further questions if something I've said makes little sense.
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.
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.