What's the difference between CALayer -drawInContext: and -renderInContext:? - iphone

What's the difference between CALayer -drawInContext: and -renderInContext: ?

When providing custom Quartz-drawn content to display within a CALayer, you can override -drawInContext: and do your custom drawing there. This is similar to -drawRect: for a UIView or NSView. Alternatively, you can set another class to be the delegate of the CALayer and implement -drawLayer:inContext: to provide custom content to a standard CALayer.
You don't override -renderInContext:, but instead you can call this on a layer to render it and all of its sublayers into a particular Core Graphics context. Note that this won't render certain types of layers (like those with OpenGL content). It also doesn't behave the way you'd expect when rendering into a PDF context, where the layers will come out as bitmapped rectangles instead of pure vector elements. To work around this, you might want to check out the Core Plot framework's CPTLayer implementation, where we bypass the normal rendering process in order to preserve the vectors in a PDF generated from our CALayer subclass.

Related

Caching Quartz Rendered Drawing Into CALayer

I'm in the process of trying to tweak the process of a game which uses Quartz to draw a moderate number of sprites, around 50, during each game loop (40 fps).
At the moment I have a very simple drawing mechanism where a single game view (UIView) iterates over all the active sprites and asks each one to render itself to a CGContext (that of the view). This works fine, but performance is starting to drop with more than 50 active objects and I'd quite like to tweak things.
I've decided that holding a CALayer for each sprite which its cached drawing is the way to go - then using Core Animation to render/rotate/scale the drawing.
I'm struggling to understand how I achieve this. Where exactly do I initially draw to? I don't have a CGContext when my sprite is initialised. Is the right approach to render to a CGImage as a buffer then set it as the content on the CALayer?
Just subclass CALayer and do your drawing in drawInContext:. The layer will then cache its contents automatically. If you don't want to subclass CALayer, you can also assign it a delegate (which must not be a UIView) and implement drawLayer:inContext:.

Drawing class to allow layer control

I would like to implement a drawing class with the help of Quartz.
I want to be able to save parts of what is drawn on separate layers. I want these layers to be retrievable, so I can delete/hide/show layers on command.
Can I save multiple CGLayerRef as a NSMutableArray property of my class and then be able to retrieve them? If yes, can you point me to an example.
If there are any flaws in the "architecture" above, please point me to alternative solutions that could help me accomplish layered control over graphs.
Thank you.
CALayers that you create, can of course be stored in NSMutableArray, and you can work with them later on, usually by animating their properties, or asking them to redraw themselves.
Usually you create a custom UIView, create and manage layers within that view. Those layers are either member variables of that view, or you store them in an array. As things are happening in your app, your view animates the layers accordingly. Usually you want to react on touch events (which you also implement in that particular view - touchesBegan/Moved...) and animate the layers.
CALayer draws itself and caches the content for as long as you call [layer setNeedsDisplay], or it's bounds (size) are changed (well, if needsDisplayOnBoundsChange is true). Practically in all my apps I did, such redrawing happens very rarely - only if data are changed, and layer needs to redraw. Animating layers, transforming their size, rotation, changing position - layer is not redrawn during any of these. Hiding, showing, changing transparency - no redraw is required.
That "drawing class" you are talking about - you actually have only two options - either you extend CALayer and overwrite drawInContext:, or you create basic CALayer, set its delegate, and there you draw in drawLayer:inContext:. I personally prefer creating delegates.

what is the context passed into drawLayer:inContext:?

In Iphone development, I want to draw context in layer.
Then the question is:
What exactly is the context passed into drawLayer:inContext:? Is it the layer's contents's context or the UIview's context?
If this is the UIView's context, which UIView it is?
Thanks in advance.
The context being passed in belongs to the CALayer also returned by that delegate method. Usually, this is a display context, but it can also be an image or PDF context if the layer is manually drawn using -renderInContext:.
CALayers can exist on their own, or be used as the backing for a UIView. All UIViews have a layer behind them, which handles the actual display of that view's content. Drawing in a view actually draws on its layer, and, likewise, drawing in a CALayer that backs a UIView will appear to draw to the view.
As I said, you can create CALayers that exist as separate entities, and add them to existing layers as sublayers for display. At some point, there will need to be a UIView that hosts all of these sublayers within its backing layer in order for these layers to be seen on the iPhone's screen.
Note that according to the UIView class reference:
Since the view is the layer’s
delegate, you should never set the
view as a delegate of another CALayer
object. Additionally, you should never
change the delegate of this layer.
This means that for a UIView's layer, you would be handling the delegate method within the UIView in almost all cases, so the layer passed in to that method would be the view's layer. Sublayers can have anything as their delegate, because they are not attached to a particular view.
There is some information here: Providing Layer Content
If you must draw the layer’s content rather than loading it from an image, you implement the drawLayer:inContext: delegate method. The delegate is passed the layer for which content is required and a CGContextRef to draw the content in.
So normally is the context of your delegate object. In the case of UIVIew, the view itself is the delegate.

Is it possible to display an CALayer without an UIView?

As far as I can tell, I need an UIView (or subclass of UIView) to display an CALayer on screen, right?
Then, what's the point of using CALayer for saving memory? The only point I see is when I would add several sublayers to a CALayer. Then those sublayers would not get copied 3 times for all the different tree types like presentation tree, render tree and so on. Is that right?
You can add CALayers as sublayers of another CALayer. Each individual layer does not have to be backed by a view, but the root layer in the hierarchy must be backed by a UIView.
The point is not to avoid having copies of the CALayers, which are quite lightweight, but to avoid having copies of UIViews or, more specifically, the graphics contexts that back UIViews. Those take up considerably more memory.

How can I blend between my UIView and my EAGLView?

I have an opengl scene rendering on an EAGLView layer and some other elements (circles and such) rendering on a UIView (which is a sibling of the EAGLView, positioned above it). Is it possible to blend colors between the two layers? I'd like to do some difference blending to get an inversion effect on the colors from EAGLView.
I've been playing around with CGBlendMode but it only seems to affect what I'm drawing in that current view. I think this has something to do with the CGContext but I'm a little hazy on the details, can I force the UIView and the EAGLView to have the same CGContext so that the blending works between them?
Help, corrections, clarifications are all appreciated. Thanks in advance,
-S
Short answer is you can not. Long answer follows.
By EAGLView you must mean the subclass of UIView that is included in the OpenGL ES Template in Xcode. What makes this class special is that the layerClass class method is overridden and return the CAEAGLLayer class instead of the CALayer class, as is default.
UIView and CALayer work in pairs. All UIView objects are backed by a CALayer, the CALayer is the object responsible for layout and rendering to screen. The UIView is a delegate to the CALayer, and is responsible for drawing it's graphics when needed.
CALayer will let it's delegate (the UIView) draw into a CGContextRef. It is one context per UIView, so you can not use CGBlendMode to blend several views since it will only function within one single UIView context.
Blending of CALayer should be done using the filter properties. These are defined for iPhone OS but the available filters are undefined according to the documentation. This is because Core Image is not available on iPhone OS at this time.
I don't think you'll be able to blend colours in that sense. The best you can do is have one completely obscuring the other, or have the top layer semi-transparent (in which case, you'll see the part underneath) - but you won't be able to do XOR type drawing.