Possible to copy CALayer from UIView? - iphone

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

Related

How does UIImage get constructed when created from an XIB?

I'm trying to do some fanciness with XIBs and that includes wanting to somehow get and store the paths of images loaded from the xib. To do this, I made some categories and did some method swizzling to override all the UIImage constructors to save their path before calling their parent constructor.
But due to Apple's black box BS with all their XIB stuff, absolutely none of the exposed constructors for UIImage seem to get called when I create a UIImageView through [UIViewController initWithNib...].
Does anybody know what function call happens or how they do this? I can't find any information whatsoever that exposes what initWithNib actually does behind the scenes.
Thanks!
EDIT:
If you're in a similar situation, you may try using the accessibilityLabel / accessibilityHint which is automatically populated with the image path. The only issue is that accessibility needs to be enabled or these values are nil.
I know the objects constructed from a Nib are being unarchived according to the NSCoding protocol; you need to override initWithCoder: in this case.
You could use swizzling to replace UIImageView's initWithCoder: method, then snoop around to see if an image name or path is available in any of the coder's keys. This might be more effective than hacking UIImage itself, since for all we know UIImageView could be using a custom subclass that you don't have access to.
the initWith... methods are meant for programatically creating UIImageView objects.
Sounds like you want to catch things as they are instantiated from XIB files. That would be the parent class UIView's [initWithCoder:] method.
As the UIView documentation says:
initWithCoder: - Implement this method if you load your view from an
Interface Builder nib file and your view requires custom
initialization.
You are both close to right - I tried doing this with UIImageView which works for initWithCoder as you guys are both suggesting. However, UIImage doesn't get initWithCoder called for some reason, instead it uses initWithCGImageStored:(CGImageRef)cgImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation.
If and when I actually get the path out of this as I desire I'll post it up here. Thanks for the help, gents.

Why not UIImage itself but UIImage view

I'm learning to develop apps for Iphone. I follow a book by Apress which I find very useful. But as nothing is perfect some issues are not well described and just skipped.In one of the applications I have to assign five images to each of the five components of a pickerview. But my question is why do/can not we use an instance of UImage itself but UIImageView to display on the picker.
As in above question you are asking why we can not use UIImage instead of UIImageView as component for UIPickerView.
UIImage is subclass of NSObject class, If we talk in terms of M-V-C its a M(model). and model in itself is nothing until its used.
UIImageView on the other hand is subclass of UIView which stands for V(view) in M-V-C so it uses your model for its contents.
So, they are two different things, not alternate. Also, if you go through UIPickerView's class documentation then you will find that it has method
- (UIView *)viewForRow:(NSInteger)row forComponent:(NSInteger)component
that configures view for specified row component. so it meanse we need view(UIImageView) and not the model class(UIImage). here for component you can return subclass of UIView.
Please refer apple's documentation.
Thanks,
You need a frame to place your photo, similarly You need UIImageView as place holder to place UIImage.
UIImage is just an Image loaded from file. View is able to handle touch events and only Views are displayed.

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

CAShaperLayer -renderInContext Doesn't Work?

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.

How can I use Core Animation to interpolate property values over time on my own classes?

Specifically, I am looking to use CA on properties of types other than
integers and doubles
CGRect, CGPoint, CGSize, and CGAffineTransform structures
CATransform3D data structures
CGColor and CGImage references
and in objects other than CALayers or NSViews
If you can do the changes yourself and the class you use is custom, you might want to add a setProgress:(float) f method to your class and use CA to animate it, then modify the desired properties as needed as a function of f.
Just do a
[[someObject animator] setValue:[NSNumber numberWithFloat:1.0] forKeyPath:#"someCustomProperty.progress"];
or if the object doesn't have an animator, create the correct CAAnimation yourself.
I am not sure exactly what you are trying to do, but if you want to use CA on other properties that is not an issue. You just need to register appropriate actions for the key path. There is an example of doing this in Apple's Core Animation documentaation, using CAAction. Specifically you implement actionForLayer:forKey: to configure the default animation behaviour of that key, and if you want to make the property animation implicitly you implement runActionForKey:object:arguments: .
As for animating objects other than CALayers, I really don't understand. Layers are the root visual entity in Core Animation. Additionally, on the iPhone every single UIView is backed on a layer, I do not believe there is anything on the iPhone's screen that is not in a layer, so I don't understand why you are worried about using animation on something that is not a CALayer.
Well, it seems I cannot do that. What I should be doing is [subclassing NSAnimation](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/AnimationGuide/Articles/TimingAnimations.html subclassing NSAnimation). This will work on a MacOS 10.4+ app, but not on Cocoa Touch, in which I cannot find any alternatives apart from using a NSTimer.