Isn't property layer of UIView be readonly? - iphone

The property layer of UIView is described in Apple's doc as following:
layer
The view’s Core Animation layer used for rendering. (read-only)
#property(nonatomic, readonly, retain) CALayer *layer
It is obviously that it is readonly. But in my project, why it could be set as following:
NSLog(#"before: %f",self.myView.laye.frame.size.width);
[self.myView.layer setAffineTransform:CGAffineTransformMakeScale(2, 2)];
NSLog(#"after: %f",self.myView.laye.frame.size.width);
//log shows us that the frame is modified
Really confused in this situation. Anyone can help me out? Thanks in advance!

The layer property is read-only, it means you cannot change the layer for another, however the CALayer object contained in the property is not immutable, you can set its own properties.
You cannot do:
self.myView.layer = newLayer;
// equivalent to [self.myView setLayer:newLayer];
But you can do:
[self.myView.layer setAffineTransform:CGAffineTransformMakeScale(2, 2)];
It's the setLayer: selector that you can't use.

CALayer is not part of UIKit. It’s part of the Quartz Core framework
while UIView class is a part of UIKit. You can read the documentation of both to know the differences
UIView inherits from NSObject and CALayer also inherits from NSObject so at the time you are doing: [self.myView.layer setAffineTransform:CGAffineTransformMakeScale(2, 2)];
You are not assigning the layer , you are actually accessing the CALayer class layer properties directly and therefore you can play with position, size, and transformation of a layer, as you can see in CALayer documentation, it allows all these things

Related

Save subclass of SKShapeNode using NSCoding

I have a subclass of SKShapenode with 2 extra attributes
#property(assign)float size;
#property(assign)float weight;
But when I save a NSMutableArray, with core data, containing some subclasses of my node, the information about size + weight is lost.
newLevel.tractorObjects = [NSKeyedArchiver archivedDataWithRootObject:arrayTractorObjects];
How can I solve this problem?
Since you appear to be using NSCoding to encode the nodes, you need to implement NSCoding in your subclass. Right now SKShapeNode is doing all of the encoding, but it doesn't know about your subclass attributes. To get your attributes encoded, you need to implement encodeWithCoder and initWithCoder. Both of these should call super's implementation so that SKShapeNode can encode itself, and then add/extract your own attributes.

What is the role of key path in Core Animation?

I'm getting acquanted with Core Animation and trying to implement simple movement along a curve line. i've found really helpful piece of code here
There is a line there:
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
Why do we need this property named keyPath? What is its role? The code works only if use the proposed NSString #"position" and nothing else. So is it a built-in constant string? It doesn't look so. Hope you see my confusion. Can you please explain in plain language how do i manage this keyPath property?
KeyPath says which animatable attribute is going to modified to
create animation effect
You can use any of the animatable property as keypath
You can find them in the header file(say CALayer) on which you
apply animation
/* The position in the superlayer that the anchor point of the layer's
bounds rect is aligned to. Defaults to the zero point. **Animatable**. */
#property CGPoint position;
Not a constant String , it is attribute name
CABasicAnimation and CAKeyframeAnimation are inherited from CAPropertyAnimation. Both use its animationWithKeyPath: method.
KeyPath has to contain Animatable Properties. Find them here Link

Does CALayer derive its contents?

After investigating Custom UITableViewCell backgroundView & selectedBackgroundView, I noticed that each layer of backgroundView & selectedBackgroundView (of each cell of a grouped table view) has a CGImageRef as its contents.
Must Apple be creating each CGImageRef and explicitly setting the contents of each layer?
Yes, a UIGroupTableViewCellBackground is its layer's delegate and sets its layer's contents in displayLayer:. I figured this out by creating MyLayer : CALayer with a setContents: method, in which I put a breakpoint, and implementing +[UIGroupTableViewCellBackground layerClass] to return [MyLayer class]. Then, I inspected the backtrace when the breakpoint was hit.

How to approach a common xib or controls across multiple view controllers?

I have an animated character coexisting with other conventional controls in an xib.
All works well. No issues there. The animation is done through UIImageView image flipping. An animation engine (state machine) triggered by a timer runs the show. The character uses six UIImageViews to render the different portions of the character that need to be animated.
Now I am looking at the possibility of the app getting more complex by adding a few more UIViewControllers (and their xib's). As you may have guessed, my character would have to exist in any xib that slides on top of the prior one.
What might be the best approach to implementing this? I suppose I could copy-paste the UIImageView's and instantiate a new animation engine (or pass a pointer) to each new xib.
I many ways what I need is for a new xib to only cover 2/3 of the prior xib and that would do it, but I am not sure that this works. Not sure about events and how they'd work with two overlapping xib's (if it's even possible).
While I experiment I thought I'd ask and see if anyone who has been here before might have an interesting approach or two to share.
Your question seems to be about ownership and not about animation. Try isolating the character into its own xib, (say Character.xib) and creating a CharacterLoader class. The CharacterLoader class would have a property of character and look something like:
CharacterLoader.h
#interface CharacterLoader : NSObject {
}
#property(nonatomic, retain) IBOutlet Character *character;
+ (Character *)loadCharacter;
#end
CharacterLoader.m
...
+ (Character *)loadCharacter {
CharacterLoader *loader = [[CharacterLoader alloc] init];
[[NSBundle mainBundle] loadNibNamed:#"Character" owner:loader options:nil];
Character *character = [loader.character autorelease];
[loader release];
return character;
}
...
If you find yourself making a lot of these loader classes, you can roll them into a single class whose role is to basically load nibs programmatically like this. Another alternative would be to just create the Character and all other shared content programmatically, but that's probably not desirable for you since you intentionally are approaching it by using nibs.
In the interest of closing the loop, this is what I ended-up doing:
I created a UIViewController subclass with a nib file. Called it "AnimatedCharacter".
"AnimatedCharacter.xib" consists of all of the elements required to create the character. It has a range of controls connected to IBOutlets and IBActions driven from "AnimatedCharacter.m". In my case "AnimatedCharacter.m" creates an NSTimer that fires-off a state machine at regular intervals to decide what to do with the character. It also implements audio playback through standard means.
Other than that there's nothing special about these files/code. In other words, I did not do anything out of the ordinary to prepare them for insertion into another UIViewController.
In the main view controller .h file:
#import "AnimatedCharacter.h"
...
AnimatedCharacter *character;
...
#property (nonatomic, retain) AnimatedCharacter *character;
Then, in the main view controller's .m file:
#synthesize character;
...
- (void)viewDidLoad
{
character = [[AnimatedCharacter alloc] init];
character.view.frame = CGRectMake(54.0, 0.0, 150.0, 150.0);
[self.view addSubview:character.view];
...
With that in place I can now do things like:
-(void)FadeOut:(SEL)selector
{
[UIView beginAnimations:#"resize" context:NULL];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:selector];
character.view.frame = CGRectMake(0.0, 0.0, 50.0, 50.0);
[UIView commitAnimations];
}
And, generally control the character as I see fit through the various member functions in "AnimatedCharacter.m". This is effectively working as a nib within a nib. Not separate threads, but both nibs are discharging their duties as expected, receiving inputs and running their respective shows.
I'd be interested in comments as to the good/bad/ugly aspects of what I've chosen to do.

Anybody successful with NSProxy of UIView (e.g., UILabel?)

I am experimenting in adding functionality to my UIViews (configuring CALayers according to state) by setting up a NSProxy subclass to stand in for any UIView I choose. Here's what I've tried:
In my NSProxy subclass, I have the following code:
#pragma mark Initialization / Dealloc
- (id)initWithView:(UIView *)view
{
delegate = view;
[delegate retain];
return self;
}
- (void)dealloc
{
[delegate release];
[super dealloc];
}
#pragma mark Proxy Methods
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation setTarget:delegate];
[anInvocation invoke];
return;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [delegate methodSignatureForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
BOOL rv = NO;
if ([delegate respondsToSelector:aSelector]) { rv = YES; }
return rv;
}
And, using my NSProxy subclass this way:
UILabel *label = [[HFMultiStateProxy alloc] initWithView:[[[UILabel alloc] initWithFrame:cellFrame] autorelease]];
label.text = text;
label.font = font;
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
label.opaque = NO;
[self addSubview:label];
Seems to work until I hit the addSubview: line.
Turning message tracing on ( instrumentObjcMessageSends(YES); ) shows the forwarding for each of the previous messages working until deep inside of the addSubview:, where this series of method calls show up in the log (the first message shown here was invoked via the proxy):
- UILabel UIView _makeSubtreePerformSelector:withObject:
- UILabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- NSMethodSignature NSMethodSignature methodReturnType
- NSMethodSignature NSMethodSignature _argInfo:
- NSMethodSignature NSMethodSignature _frameDescriptor
+ UILabel NSObject resolveInstanceMethod:
- UILabel NSObject forwardingTargetForSelector:
- UILabel NSObject forwardingTargetForSelector:
- UILabel NSObject methodSignatureForSelector:
- UILabel NSObject methodSignatureForSelector:
- UILabel NSObject class
- UILabel NSObject doesNotRecognizeSelector:
And I get the following error:
2011-02-20 16:38:52.048 FlashClass_dbg[22035:207] -[UILabel superlayer]: unrecognized selector sent to instance 0x757d470
if I do not use an NSProxy subclass and instead use a UILabel subclass (HFMultiStateLabel), it works fine. Here is the message trace that occurs once addSubview: is called (HFNoteNameControl is the superview of the label):
- HFNoteNameControl UIView addSubview:
- HFNoteNameControl UIView _addSubview:positioned:relativeTo:
- HFMultiStateLabel UIView superview
- HFMultiStateLabel UIView window
- HFNoteNameControl NSObject isKindOfClass:
- HFNoteNameControl NSObject class
- HFNoteNameControl UIView window
- UIWindow NSObject isKindOfClass:
- UIWindow NSObject class
- HFNoteNameControl UIView _shouldTryPromoteDescendantToFirstResponder
- HFMultiStateLabel UIView _isAncestorOfFirstResponder
- HFMultiStateLabel UIView _willMoveToWindow:withAncestorView:
- HFMultiStateLabel UIView _willMoveToWindow:
- HFMultiStateLabel UIView willMoveToWindow:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- HFMultiStateLabel UIView willMoveToSuperview:
- HFMultiStateLabel UIView _unsubscribeToScrollNotificationsIfNecessary:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- CALayer CALayer superlayer
I can verify that each of the methods up until -superlayer are called successfully when using NSProxy. For some reason, with the NSProxy, superlayer on UILabel is being called instead of CALayer. Perhaps somewhere something gets confused and UILabel is inserted into the sublayers instead of its CALayer?
Does the UIKit do some sort of optimizations that bypass the normal mechanism that NSProxy hooks into?
PS I have only tried this in the Simulator, not the device. Would that behavior be any different?
I was trying to solve the same issue - use NSProxy with UIView (in my case UITableViewCell) when I encountered this problem. I logged all calls to the console:
...
App[2857:c07] MyHeaderCell: --- method signature for: _unsubscribeToScrollNotificationsIfNecessary:
App[2857:c07] MyHeaderCell: --- _unsubscribeToScrollNotificationsIfNecessary:
App[2857:c07] MyHeaderCell: --- method signature for: _makeSubtreePerformSelector:withObject:
App[2857:c07] MyHeaderCell: --- _makeSubtreePerformSelector:withObject:
App[2857:c07] +[MyHeaderCell superlayer]: unrecognized selector sent to class 0x1331f8c
App[2857:c07] CRASH: +[SMSHeaderCell superlayer]: unrecognized selector sent to class 0x1331f8c
App[2857:c07] Stack Trace:...
It crashes on the unrecognized selector exception.
Normally, the object is asked the - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel method first and when that is returned, it invokes the - (void) forwardInvocation:(NSInvocation *)invocation in the proxy. This way we can redirect the messages. If there is not NSMethodSignature returned, the doesNotRecognizeSelector: method is called on the object. So we get even unrecognized selector calls.
This works for instance methods, but this crash is caused by a class method, which we have no power over - the object itself is not called (the class is). I wanted to force the runtime to call my proxy class even for class methods by overriding the getter of my NSProxy subclass
- (Class) class
{
return _myRealClass;
}
Which did not work. So NSProxy is not enough to do this. Right now I'm trying to use the NSObject instead of NSProxy to achieve all the desired behavior and since NSObject has the + (BOOL)resolveClassMethod:(SEL)sel method which might be useful. I will edit this post once I found out if NSObject is better suited for this.
//Edit
It seems that the problem is that with NSProxy, superlayer is being called on UIView instead of CALayer.
So it really seems like a UIKit shortcut problem - they're not sending a regular message call (speed optimization I would guess).
Anyways, this I am searching for a way to get around this now.
I gave up trying. I've come to the conclusion that NSProxy is such an underused object that it's potential for uses beyond Apple examples has not been fully explored nor debugged. In short, I believe that NSProxy is not ready to be used as a generic way to extend an object's functionality without subclassing or adding a category.
In the old days, I would have used a poseAsClass call to implement my desired functionality.
My solution ended up something like this:
I added a category to UIView that added additional properties. These property implementations forwarded their set & get messages to a "addOn" property of the UIView that I also put into the category. The default value of this "addOn" property in the UIView's category implementation is, of course, nil. (I could have implemented a static hash table to enable associating an AddOn instance for any UIView, but it struck me as a risky ploy to manage with the retain counts properly.)
The "AddOn" class had extra code in it to directly manipulate the UIView, and it added extra drawing code in it.
For each type of UIView that I wanted to add this added functionality, I had to subclass it with code that:
a) Created an instance method and corresponding property code for the "AddOn" class
b) Subclassed any functions I covered to give the "AddOn" code a chance to add its functionality.
Each of these subclasses has essentially the same code in it to forward the desired functionality to the AddOn instance.
SO, I ended up minimizing code duplication as much as I could, but each of the UIView's descendant subclasses that enable use of the the "AddOn" functionality ends up duplicating code.
It appears that I could have further minimized code duplication by using class method manipulation functions, but that learning curve and further obfuscation of the code deterred me from following that path.
I have never tried using NSProxy with views, but I have done something similar by using a custom view class to display another view. Maybe the system requires an actual view and not a proxy object. There are two ways you could use a "proxy" view:
Make the proxied view a subview of the proxy view. The proxy would take the frame, autoresizing mask, etc. from the proxied view, then add the proxied view as its subview and set its frame to be the proxy view's bounds, and its autoresizing mask so that it always fills the proxy view. When the proxied view is removed, any settings are copied back into it from the proxy view. Any properties not copied into the proxy are passed to the proxied view using forwarding.
The proxy view passes almost every message to the proxied view. The proxy view does not override the lock/unlockFocus, display, etc. methods. It overrides drawRect: to call drawRect: on the proxied view.
After trying the same thing, and searched for the error (which got me here), I tried to circumvent the problems... It wasn't pretty.
Identifying the root problem was easy. Somewhere in the framework, Apple is using direct pointer access to the variables in UIView subclasses. If you check the headers, the variables are declared with #package access identifier.
What I basically tried was:
Create a proxy class at runtime with ivars copied from the UIView class definition, and then set the values of these pointers to the objects in the UIView. Couldn't get far there.
Declare just the CALayer * in the proxy subclass, and only copy that pointer from the protected UIView instance. Worked, but I think it was buggy? It didn't work with auto layout at all, though, so I decided to move away from that solution.
The code I tried can be found in the RTLSegmentedControl repo under the proxy-pattern branch
I also wrote a blog post about the details.