CAShaperLayer -renderInContext Doesn't Work? - iphone

I am able to create a UIImage from a Core Animation layer using the following code:
- (UIImage*)contentsImage;
{
UIGraphicsBeginImageContext([self bounds].size);
[self renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
This code is in my CALayer derived class. The issue I am running into is that I have two CAShapeLayers that are child layers of my layer that do not get rendered to the resulting image. If I add standard CALayers as children they get rendered fine. The Apple docs say:
Renders the receiver and its sublayers
into the specified context.
It also says that it's been available since iPhone OS 2.0. Wondering if there is something I'm missing or if I should file a radar.
Any ideas what might keep the child CAShapeLayers from getting drawn to the image?
Thanks.

The CALayer machinery calls renderInContext to create its bitmapped contents property. But in a CAShapeLayer, the path property is not actually rendered to its contents as seen by this note in the header:
The shape as a whole is composited
between the layer's contents and its
first sublayer.
It stands to reason that renderInContext won't actually render the CAShapeLayer path onto your context. I haven't actually tried this out for myself however.

Don't know if its relevant to you but there is a note in the CALayer documentation for renderInContext that says :
**Important**: The Mac OS X v10.5 implementation of this method does not
support the entire Core Animation composition model. QCCompositionLayer,
CAOpenGLLayer, and QTMovieLayer layers are not rendered. Additionally,
layers that use 3D transforms are not rendered, nor are layers that specify
backgroundFilters, filters, compositingFilter, or a mask values.
Future versions of Mac OS X may add support for rendering these layers
and properties.
Anyways, I ran into a similar problem when using the UIView drawRect function in conjunction with drawing in an image context. The overall UIView that contained subviews would not draw its subviews if I called drawRect (which makes sense now actually since it says in the documentation if you call drawRect you are responsible for filling that entire area regardless of super and subview implementations). I solved my problem by just called drawRect on all my subviews, passing them their own frames.
So I would suggest maybe switching away from renderInContext and use CALayer's drawInContext instead? You'll need to override the method since it doesn't do anything by default. Your subclasses will also need to move the contexts to their appropriate frames. Also to be safe you might want to check that none of the code you add affects normal rendering of these layers.

I filed a radar on this. I can't see any reason in the docs that it shouldn't work.I will respond back here if/when Apple replies to the radar.

Related

On iOS, if a subclass of CALayer is used with an empty display method, then app seems to go into infinite loop?

If a Single View app is created, with a FooView that subclasses UIView, and do a
NSLog(#"hello");
in drawRect, then it is printed.
And if I create a subclass of CALayer called CoolLayer, and add this method to FooView.m:
+(Class) layerClass {
return [CoolLayer class];
}
and at the end of FooView.m's drawRect, do a
NSLog(#"layer's class is %#", self.layer.class);
then CoolLayer is printed. So now the view's underlaying layer is CoolLayer.
But when the following is added to CoolLayer.m:
-(void) display {
}
which is the method that is automatically called to redraw the layer (similar to drawRect), then no NSLog whatsoever was printed. It might be that the app went into an infinite loop. (even my touchesBegan that prints out NSLog messages is not printing). If a breakpoint is set at display, it will stop there once but when I continue the program, it will never arrive at display again. What is wrong with this and how can it be fixed?
The layer's display method will not be called again unless the layer is dirty, i.e. set to need a redisplay. This is usually a good thing, and is why you don't see the method being called more than once.
Also, the normal implementation of display will call the drawInContext: method. Since you override this in your subclass, the drawRect: method of the view is never called. You need to either replicate the standard behavior of CALayer, or call the superclass' display method in your own implementation.
That does not sound like an infinite loop. If you were in an infinite loop your app would freeze, and after a few seconds the springboard app would kill it for being unresponsive.
Call setNeedsDisplay or setNeedsDisplayInRect on your layer to make it "dirty" and require drawing again. Note that you don't want to call setNeedsDisplay any more than you have to, because it takes a lot of work to re-render the layer and push it's contents onto the screen. Only display when something has changed
You're not seeing an infinite loop here. If you were, as Duncan points out, you'd eventually crash either due to the watchdog timer or from an infinite recursion that would be immediately obvious in the stack trace you'd see in the debugger.
If you put an NSLog into your UIView's -drawRect: method, then override its default layer class with your own custom CALayer that does its own drawing, your UIView's -drawRect: would no longer be called. Drawing would now be handled by your custom backing layer.
As described in the "Providing CALayer Content by Subclassing" subsection of the Core Animation Programming Guide, you typically override -display in your CALayer if you want to somehow customize the contents CGImageRef for your layer. Normally, you'll be overriding -drawInContext: if you want to render custom Quartz drawing within your CALayer. Making your overridden -display method totally blank, and not writing anything to the contents property, is not standard behavior, so I'm not surprised that you're seeing odd results from doing that.
Based on your series of recent questions, I highly recommend you stop and spend some time reading the Core Animation Programming Guide and looking at some sample code involving CALayers before proceeding further.

UIWebView: Tracking screen updates (dirty regions)

I'm trying to detect animations and other screen updates as they happen inside a UIWebView. I would like to get the rectangles of areas in the UIWebView that have been modified since the last refresh.
I think really what I'm looking for is for a way to "trap" the calls that UIWebView makes to setNeedsDisplayInRect. Is there a way to do that? Can I somehow subclass UIWebView's underlying CALayer object in a way that would allow me to catch those calls as they come in from UIWebView?
There's no good way of doing that. You can try grabbing a snapshot of the UIWebView's CALayer and comparing it to the previous snapshot, but I've had a lot of trouble getting reliable snapshots of UIWebViews.
Use an Objective-C category (#implementation CALayer (MyCALayer)) - like you're already doing based on your update - to trap the calls going from UIWebView to CALayer.
Then, use Method Swizzling to relay your category overrides to the original CALayer object.

Cocoa-Touch: How to set the interpolation quality to be used by a UIImageView?

I have a UIImageView showing a image that is larger than it's frame.
It's set to rescale the image to fit it's frame. But, the image is scaled with a low quality filter.
I've read here that this is caused by it using a low interpolation quality.
How can I get it's context to CGContextSetInterpolationQuality to kCGInterpolationHigh?
From "UIImageView scaling/interpolation", this is the most streamlined way to do it if you can:
[[yourimageview layer] setMagnificationFilter:kCAFilterTrilinear]
Be sure to #import <QuartzCore/CALayer.h>
A warning on kCAFilterTrilinear: "Some renderers may ignore this, or impose additional restrictions, such as source images requiring power-of-two dimensions."
UIImageView does not offer this functionality, though UIImage has an undocumented _imageScaledToSize:interpolationQuality: method if I remember correctly.
Since UIImageView draws directly to the display, subclassing and overriding drawRect: is no option (thanks to Prody for pointing this out). The only option I see is to create a custom UIView subclass with a custom drawrect: implementation.
CGContextSetInterpolationQuality is a function. You need to call it with whatever parameters are appropriate for your situation.
http://developer.apple.com/mac/library/qa/qa2001/qa1186.html

Possible to copy CALayer from UIView?

Here's my setup: I have a CALAyer to which I want to add sublayers. I create these sublayers by setting upa UILabel and then adding the UILables layer to my main layer. Of course this leaves the heavy UILabel object hovering around in the background. Is it possible to get the layer with all its content from a UIView and get rid of the UIView itself?
I already tried this:
UILabel* label;
[...]
[mainLayer addSublayer:[label.layer copy]];
[label release];
But whenever I release the UIView, the content of the layer is also removed. Is this even possible or does the UIView's layer always need the UIView itself to show its content? I thought of the layer as a kind of canvas, to which the UIView paints. I guess I could be wrong with this assumption :)
I can't understand why you wouldn't be able to copy a layer. True a layer is an "integral part" of a UIView. But in the end it is just another object with several properties.
Actually there is a method for CALayer called:
- (id)initWithLayer:(id)layer
But it isn't intended to make a copy of a layer. (You can read Apple's docs for the reasoning why)
CALayer does not conform to NSCopying, so you have two options:
Subclass it and implement
"copyWithZone:" (and conform to
NSCopying)
Write a method/function that will
return a "copy" of a CALayer
Regardless of which way you choose, the question you have to ask is: Which properties of the CALlayer do you want to copy?
Let's say you want to copy just the contents and frame:
CALayer copyLayer = [CALayer layer];
copyLayer.contents = theLayerToBeCopied.contents;
copyLayer.frame = theLayerToBeCopied.frame;
return copyLayer;
You could go through and copy every property of the layer, or just copy the ones you need. Might be a good method to put in a CALayer category.
It is not possible: a layer is a property of a UIView. Therefore, when you release the UIView, its layer is gone. Your idea of thinking of the layer as a kind of canvas is not wrong. But, the layer is an integral part of its UIView.
UIViews are not that heavy in iOS. For the most part, you can think of them as a supporting wrapper around a CALayer. Unlike on Mac where a NSView doesn't have to be backed by a CALayer, that's not true in iOS. All UIView instances are CALayer-backed and that's where most of the heavy lifting is. Apple's docs even say things like 'Don't bother animating a UIView's layer. Animate the UIView directly as it's essentially just sending it all down to the layer anyway.' (Paraphrased, of course.)
Point being, unless you specifically need UILayer instances for something that's not directly supported by a UIView, just stick with working with UIViews entirely and you should be good to go.
UILabel really isn't a very complex control. I suggest you give up this line of attack and learn how to draw what you want using Quartz into a fresh CGImage. You can then assign this to the contents property of the CALayer without having to worry about all this other stuff.
You can also circumvent UILabel entirely and create your text with a CATextLayer.
use CAReplicatorLayer, you will be able to completely replicate that layer

Custom view transition in OpenGL ES

I'm trying to create a custom transition, to serve as a replacement for a default transition you would get here, for example:
[self.navigationController pushViewController:someController animated:YES];
I have prepared an OpenGL-based view that performs an effect on some static texture mapped to a plane (let's say it's a copy of the flip effect in Core Animation). What I don't know how to do is:
grab current view content and make a texture out of it (I remember seeing a function that does just that, but can't find it)
how to do the same for the view that is currently offscreen and is going to replace current view
are there some APIs I can hook to in order to make my transition class as native as possible (make it a kind of Core Animation effect)?
Any thoughts or links are greatly appreciated!
UPDATE
Jeffrey Forbes's answer works great as a solution to capture the content of a view.
What I haven't figured out yet is how to capture the content of the view I want to transition to, which should be invisible until the transition is done.
Also, which method should I use to present the OpenGL view?
For demonstration purposes I used pushViewController. That affects the navbar, though, which I actually want to go one item back, with animation, check this vid for explanation:
http://vimeo.com/4649397.
Another option would be to go with presentViewController, but that shows fullscreen.
Do you think maybe creating another window (or view?) could be useful?
While I cannot completely answer your question without doing some more research of my own, I can help a bit:
-In order to get the view of a UINavigationController, you need to take a screenshot. The easiest way to do this is by grabbing it into a UIImage:
UIGraphicsBeginImageContext(self.view.frame.size);
[[self.view layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage* test = UIGraphicsGetImageFromCurrentImageContext();
UIImageView* view = [[UIImageView alloc] initWithImage:test];
UIGraphicsEndImageContext();
I am not sure if you can render a GLContext (not familiar on the phone) into a CGImage, but I would do something like that (and init a UIImage from that). I would prerender every frame of the animation you are trying to do and slap it into an UIImageView using the animation stuff provided within. That is, if your animation is simple enough. Otherwise, it might come down to writing your own animation function :-/
I have just put together a transition class to implement your own transition animation in OpenGL ES.
Feel free to read about it here
There are two example transitions in the project, feel free to add you own to it.
I think the function you might be thinking of is http://www.opengl.org/sdk/docs/man/xhtml/glCopyTexImage2D.xml ... you set the viewport to the texture size and then draw as usual, then do glCopyTexImage2D to copy the scene onto a texture.
or you should look into FrameBuffer Objects. The default OpenGL template in XCode uses these. Just generate the example project to see how those work.
I recently write some transitioning animation betweeen view controllers like you. If you want to get any extra info from the invisible view, you can try delaying the transition like this :
- (void)animationFromModalView:(UIView *)modalView toMasterView:(UIView *)masterView
{
[masterView setNeedsLayout];
[masterView layoutIfNeeded];
[self performSelector:#selector(delayAnimationFromModalViewToMasterView) withObject:nil afterDelay:.1f];
}