Setting the frame.size using the dot operator - objective-c

I have a UIController and as you all know UIController is associated to a view and you can access it using the getter and setters methods which are synthesized
UIController controller = init code ..
..
controller.view -> this gives me my UIView object which retained and autoreleased, this will be synthesized get method(If at all my synthesized getmethod understanding is correct)
controller.view.frame -> this gives me my CGRect struct
controller.view.frame.size -> CGSize struct
why cannot I assign a value directly to this frame structure
controller.view.frame.size.width = 20;
for the above statement I get this error "lvalue required as left operand of assignment"
This is a normal c dot operator I think it should work.Please enlighten if I am missing anything

Using the dot operator in this situation is using the frame getter method behind the scenes. Since the frame property is a CGRect, which is a simple C struct, frame returns you a copy of the value, not a pointer to the value. Changing it will modify the CGRect you have copied locally on the stack, not the CGRect of your view's frame property. To update the actual frame property you must go through the setter method [yourView setFrame:yourNewFrame]; or yourView.frame = yourNewFrame;.

the easiest to solve that is to set the whole frame again
controller.view.frame = CGRectMake(controller.view.frame.origin.x, controller.view.frame.origin.y, 20, controller.view.frame.size.height);
the reason why this is not working directly is described in James link, it has only getters.

Dot syntax is just syntax sugar.
In this case, this code:
controller.view.frame.size.width = 20;
Is actually this code:
[[controller view] frame].size.width = 20;
In C terms, that's like this:
ViewGetFrame(ControllerGetView(controller)).size.width = 20;
Bottom line is you can't set a subfield of a function result this way. And even if you could, it wouldn't affect the original but only a copy.

Related

Non-animatable properties in subclasses of CALAyer

I have defined a subclass of CALayer with an animatable property as discussed here. I would now like to add another (non-animatable) property to that layer to support its internal bookkeeping.
I set the value of the new property in drawInContext: but what I find that it is always reset to 0 when the next call is made. Is this so because Core Animation assumes that this property is also for animation, and that it "animates" its value at constant 0 lacking further instructions? In any case, how can I add truly non-animatable properties to subclasses of CALayer?
I have found a preliminary workaround, which is using a global CGFloat _property instead of #property (assign) CGFloat property but would prefer to use normal property syntax.
UPDATE 1
This is how I try to define the property in MyLayer.m:
#interface MyLayer()
#property (assign) CGFloat property;
#end
And this is how I assign a value to it at the end of drawInContext::
self.property = nonZero;
The property is e.g. read at the start of drawInContext: like so:
NSLog(#"property=%f", self.property);
UPDATE 2
Maybe this it was causes the problem (code inherited from this sample)?
- (id)actionForKey:(NSString *) aKey {
if ([aKey isEqualToString:#"someAnimatableProperty"]) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:aKey];
animation.fromValue = [self.presentationLayer valueForKey:aKey];
return animation;
}
return [super actionForKey:aKey]; // also applies to my "property"
}
To access your standard property from within the drawing method, during an animation, you need to make a few modifications.
Implement initializer
When CoreAnimation performs your animation, it creates shadow copies of your layer, and each copy will be rendered in a different frame. To create such copies, it calls -initWithLayer:.
From Apple's documentation:
If you are implementing a custom layer subclass, you can override this method and use it to copy the values of instance variables into the new object. Subclasses should always invoke the superclass implementation.
Therefore, you need to implement -initWithLayer: and use it to copy manually the value of your property on the new instance, like this:
- (id)initWithLayer:(id)layer
{
if ((self = [super initWithLayer:layer])) {
// Check if it's the right class before casting
if ([layer isKindOfClass:[MyCustomLayer class]]) {
// Copy the value of "myProperty" over from the other layer
self.myProperty = ((MyCustomLayer *)layer).myProperty;
}
}
return self;
}
Access properties through model layer
The copy, anyway, takes place before the animation starts: you can see this by adding a NSLog call to -initWithLayer:. So as far as CoreAnimation knows, your property will always be zero. Moreover, the copies it creates are readonly, if you try to set self.myProperty from within -drawInContext:, when the method is called on one of the presentation copies, you get an exception:
*** Terminating app due to uncaught exception 'CALayerReadOnly', reason:
'attempting to modify read-only layer <MyLayer: 0x8e94010>' ***
Instead of setting self.myProperty, you should write
self.modelLayer.myProperty = 42.0f
as modelLayer will instead refer to the original MyCustomLayer instance, and all the presentation copies share the same model. Note that you must do this also when you read the variable, not only when you set it. For completeness, one should mention as well the property presentationLayer, that instead returns the current (copy of the) layer being displayed.

converting dot syntax to bracket syntax on a struct

I have a property of CGSize:
#property (nonatomic) CGSize descriptionSize;
'
#synthesize descriptionSize = _descriptionSize;
I can access the height through the dot syntax:
self.descriptionSize.height = 35;
but how does this work with the bracket syntax?
[self setDescriptionSize:???];
Looked stupid simple to me, but I can't get the clue. Thanks in advance!
This is one of the pitfalls of dot notation for properties: Those two dots in self.descriptionSize.height look the same but mean very different things. The first is a property accessor which maps to a "get descriptionSize" method, but the second is an old-school struct reference. The first dot returns a CGSize scalar, NOT a pointer to the size value in the object. When the second dot sets the height in that returned CGSize, it's setting a value on the stack instead of changing the value in the object. This is how you have to do it:
CGSize size = self.descriptionSize;
size.height = 35;
self.descriptionSize = size;
…or the equivalent without properties dot notation:
CGSize size = [self descriptionSize];
size.height = 35; // we still use the dot here: size is a struct not an object
[self setDescriptionSize:size];
The implementation of descriptionSize will return a copy of the CGSize struct, so you can't work directly with that and hope it will work. What you need to do is get the whole of the CGSize struct, modify it, and then pass it back in:
CGSize size = [self descriptionSize];
size.height = 35;
[self setDescriptionSize:size];
However given you are working on a property of self and the property isn't an object, which requires memory management, the most efficient way of modifying the size is:
_descriptionSize.height = 35;
However you'd use the former getter/setter approach if:
The object was not self.
You had manually written the setter method to do something as a side-effect of changing the size (for example invalidating bits of the view in order to automatically update the view).
Dot syntax can mean two different things: Either a struct reference (CGSize is a C struct), or an objective-C message send.
Theoretically, if you have a method like - (void)doSomething;, you could call it like this: myObject.doSomething; //bad style. Don't do this. Dot syntax is not meant for calling methods that actually do stuff, other than getting or setting values (although nothing in the language or the IDE is going to stop you).
Synthesizing properties creates accessor methods: - (myType)myProperty and - (void)setMyProperty:(myType)newValue. Here, dot syntax lets you access the getter in the ordinary way (because the getter is an ordinary Objective-C method), and has a special case for the setter: myObject.myProperty = newValue gets translated to [myObject setMyProperty:newValue].
This means you can switch between dot syntax and Objective-C style message sending syntax for properties (and technically for all other parameter-less Objective-C method sends), but you must use dot syntax to access struct members. Structs are not objects, and they know now methods.

Typecasting return value of methods when returned value is parent class of the typecast?

I have code similar to this.
MySubclassOfUIView *view = [aUIPickerView viewForRow:4 forComponent:0];
The viewForRow:forComponent method of UIPickerView returns a UIView. MySubclassOfUIView is exactly that: a subclass of UIView.
The UIPickerView delegate (not mentioned here) uses an array of MySubclassOfUIView objects to populate the rows of the UIPickerView components. Thus, I know the viewForRow:forComponent method is really going to be returning a pointer to an object of type MySubclassOfUIView.
Xcode gives me this warning.
Incompatible pointer types initializing 'MySubclassOfUIView*' with an expression of type 'UIView*'.
So I figure that I'll typecast it to fix the warning, which gives me this code.
MySubclassOfUIView *view = (MySubclassOfUIView*)[aUIPickerView viewForRow:4 forComponent:0];
And the warning goes away.
Please forgive my shaky C and Objective-C skills, but am I doing the right thing (as far as the context given so far)? Is there some other better way to handle this situation?
If you are absolutely sure that it will return a MySubclassOfUIView, then it is OK to do this. If there is any chance that it could return something else (such as you made a mistake and added the wrong thing to the array), then you should check the type and use a temporary variable.
UIView *temp = [aUIPickerView viewForRow:4 forComponent:0];
NSAssert([temp isMemberOfClass:[MySubclassOfUIView class]],[NSString stringWIthFormat:#"aUIPickerView returned the wrong class (%#)",[temp class]]);
MySubclassOfUIView *theView = (MySubclassOfUIView*)temp;
What you can do is:
MySubclass* subFoo = [[MySubclass alloc] init];
MySuperclass* superFoo = subFoo;
What you shouldn't do is:
MySuperclass* superFoo = [[MySuperclass alloc] init];
MySubclass* subFoo = superFoo;
This is, because your Subclass will have all properties, selectors, etc from the Superclass. But the Superclass won't have all (..) of the Subclass.
For the rest, see ughoavgfhw's answer.

Passing arguments by value or by reference in objective C

I'm kind of new with objective c and I'm trying to pass an argument by reference but is behaving like it were a value. Do you know why this doesn't work?
This is the function:
- (void) checkRedColorText:(UILabel *)labelToChange {
NSComparisonResult startLaterThanEnd = [startDate compare:endDate];
if (startLaterThanEnd == NSOrderedDescending){
labelToChange.textColor = [UIColor redColor];
}
else{
labelToChange.textColor = [UIColor blackColor];
}
}
And this is the call:
UILabel *startHourLabel; // This is properly initialized in other part of the code
[self checkRedColorText:startHourLabel];
Thanks for your help
Objective-C only support passing parameters by value. The problem here has probably been fixed already (Since this question is more than a year old) but I need to clarify some things regarding arguments and Objective-C.
Objective-C is a strict superset of C which means that everything C does, Obj-C does it too.
By having a quick look at Wikipedia, you can see that Function parameters are always passed by value
Objective-C is no different. What's happening here is that whenever we are passing an object to a function (In this case a UILabel *), we pass the value contained at the pointer's address.
Whatever you do, it will always be the value of what you are passing. If you want to pass the value of the reference you would have to pass it a **object (Like often seen when passing NSError).
This is the same thing with scalars, they are passed by value, hence you can modify the value of the variable you received in your method and that won't change the value of the original variable that you passed to the function.
Here's an example to ease the understanding:
- (void)parentFunction {
int i = 0;
[self modifyValueOfPassedArgument:i];
//i == 0 still!
}
- (void)modifyValueOfPassedArgument:(NSInteger)j {
//j == 0! but j is a copied variable. It is _NOT_ i
j = 23;
//j now == 23, but this hasn't changed the value of i.
}
If you wanted to be able to modify i, you would have to pass the value of the reference by doing the following:
- (void)parentFunction {
int i = 0; //Stack allocated. Kept it that way for sake of simplicity
[self modifyValueOfPassedReference:&i];
//i == 23!
}
- (void)modifyValueOfPassedReference:(NSInteger *)j {
//j == 0, and this points to i! We can modify i from here.
*j = 23;
//j now == 23, and i also == 23!
}
Objective-C, like Java, only has pass-by-value. Like Java, objects are always accessed through pointers. "objects" are never values directly, hence you never assign or pass an object. You are passing an object pointer by value. But that does not seem to be the issue -- you are trying to modify the object pointed to by the pointer, which is perfectly allowed and has nothing to do with pass-by-value vs. pass-by-reference. I don't see any problem with your code.
In objective-c, there is no way to pass objects by value (unless you explicitly copy it, but that's another story). Poke around your code -- are you sure checkRedColorText: is called? What about [startDate compare:endDate], does it ever not equal NSOrderedDescending? Is labelToChange nil?
Did you edit out code between this line
UILabel *startHourLabel;
and this line?
[self checkRedColorText:startHourLabel];
If not, the problem is that you're re-declaring your startHourLabel variable, so you're losing any sort of initialization that was there previously. You should be getting a compiler error here.
Here are the possibilities for why this doesn't work:
the label you pass in to checkRedColorText is not the one you think it is.
the comparison result is always coming out the same way.
... actually, there is no 3.
You claim you initialised startHourLabel elsewhere, but, if it is a label from a nib file, you should not be initialising it at all. It should be declared as an IBOutlet and connected to the label in the nib with interface builder.
If it is not a label in the nib i.e. you are deliberately creating it programmatically, you need to check the address of the label you initialise and check the address of the label passed in to checkRedColorText. Either NSLog its address at initialisation and in checkRedColorText or inspect it with the debugger.

Subclassing and Casting in Objective C

I came across a strange problem today. I created a subclass of UIView and added only 1 method to the template code provided by xcode.
#interface FloatView : UIView {
}
- (void)floatTest:(CGFloat)x;
#end
- (void)floatTest:(CGFloat)x {
NSLog(#"float was %f", x);
}
Then in my appDelegate I had code like this:
UIView *floatView = [[FloatView alloc] init];
[floatView floatTest:10.0f];
Pretty simple, right? What should this print out? I thought it would something like "10.0000", but no, it prints out "0.000000".
I wrestled with this for hours, trying to figure out what I was doing wrong, and then I changed the code in my appDelegate to
FloatView *floatView = [[FloatView alloc] init];
[floatView floatTest:10.0f];
Only then, did it print out the expected "10.0000". Why is this so? I've declared FloatView as a subclass of UIView, shouldn't I be able to assign a FloatView object to a UIView pointer without problems?
Even though floatView was declared a pointer to a UIView, it's really a floatView and it should be able to handle the floatTest message? Am I totally off base here?
Actually, polymorphism is working as expected. If it didn't work, nothing would have been printed (in your example, 0.0000 is being printed). The thing is, while your instance actually responds to testFloat:10.0f message, since the compiler can't statically see the method declaration (as UIView class doesn't declare such a method), it assumes that your method takes ... as argument and returns id.
When CGFloat is passed to a method that expects variable number of arguments (...), it's promoted to double. Thus, the receiving method is passed a double argument and thinks it's a float and it doesn't get printed correctly.
You can verify this behavior by changing NSLog line to:
NSLog(#"%f", *(double*)&x);
When the compiler sends the message to FloatView* rather than a UIView*, it can find the exact signature of the method. It can see it really expects CGFloat and doesn't promote the argument to double. As a result, it works correctly.
Additionally, if UIView* contained the method declaration that took a CGFloat, the compiler would call the method appropriately. To summarize, this is not a polymorphism issue; it's a missing method signature issue.