myView.frame.origin.x = value; does not work - But why? - objective-c

I know that I can't use this:
myView.frame.origin.x = 25.0;
and that I have to use this instead:
CGRect myFrame = myView.frame;
myFrame.origin.x = 25.0;
myView.frame = myFrame;
And I'm doing it all the time, but I don't know why I must do it that way. I would like to fill that gap in my understanding. Can someone explain ?
Nowadays Xcode gives you "Expression not assignable". Some time ago you got a compile error "Lvalue required as left operand of assignment".

There are two distinct dot syntaxes being used here. They look the same, but they do different things depending on what they are operating on and what is being done with it:
The first myView.frame is a shorthand for [myView frame], a method call that returns a CGRect struct by value.
myFrame.origin.x is accessing ordinary struct members in the traditional C fashion.
The second myView.frame is again a shorthand, but because the statement is an assignment it translates to calling a different method, [myView setFrame:myFrame].
In your single-line top example, you get a copy of the rect and set its x, but never copy it back to the view. You have to explicitly differentiate between the method calls, the dot syntax sugar can't magic them into a single call.

The reason this does not work is due to the mixing of two syntaxes.
First you have "." as a shortcut for calling the accessor functions (an Objective-C feature).
So
a.b becomes [a getB];
a.b = 5 becomes [a setB:5];
And then theres "." as direct struct member access (pure C). So
a.b really is a.b;
a.b really is a.b = 5;
Combining this in a set-value-case like this, doesn't work.
Because ...
If you could call
myView.frame.origin.x = 25.0;
The "myView.frame" part equals [myView getFrame] and you get a copied CGRect frame (a C struct)
The "myView.frame.origin" gives you a CGPoint origin (also a struct) of the copied CGRect
The "myView.frame.origin.x = 25.0" gives you a CGFloat x of the origin and now you want to assign something to it and here comes the problem...
You try to set a variable of a struct of a struct, which is ok, but there is no pointer from the UIView to the struct, so it is copied instead. So you copy and then you set and then you expect that the set action is somehow forwarded through the initial get to the UIView, well and this just doesn't work.
Of course you could wonder why Apple hasn't just created a shortcut, so that in the end your copied frame is automatically reinjected into a auto appended setFrame call, I guess you just have to live with how it is.
So remember, it would work if you'd get a pointer to the frame, but you don't, you get a copied struct instead.
So if you expect myView.frame.origin.x = 25.0 to work you indirectly expect your call to be automagically translated into some sort of
[myView setFrame:[myView getFrame:frame].origin.x = 25.0].
Well I guess you can admit that this looks wrong.
Also imagine if you'd get a direct pointer to the CGRect frame and you'd change something through that pointer, how would the UIView know that it's size has changed and that it has to update itself ? If on the other hand a [myView setFrame:newFrame] call is made, then UIView can do all the necessary readjusting itself.

A CGRect is a struct, which is something from standard C. A CGRect is not an Objective C object, so when you assign to one of its members, no setter method is called. Without a setter method being called, UIKit will not be able to know that anything has changed, and so will not be able to update the screen display.
Edit: as has been pointed out, the assignment will be to a copy of the struct.

When you manipulate the data directly, no accessor is called, so the UI cannot update itself - or inform any other component that wants to know about changes.
Edit: As pointed out by walkytalky, you will get a copy of the data, so changing it doesn't have any effect on the original anyway. The following example will show this:
UIView *aView = [[UIView alloc] initWithFrame:CGRectMake(50,50,100,100)];
NSLog(#"%f", aView.frame.origin.x); // will give 50
aView.frame.origin.x = 17; // operates on a copy of the rect only
NSLog(#"%f", aView.frame.origin.x); // will still give 50

Related

Manipulating origin of a CGRect property through self. notation does not work but manipulating through ivar works [duplicate]

I know that I can't use this:
myView.frame.origin.x = 25.0;
and that I have to use this instead:
CGRect myFrame = myView.frame;
myFrame.origin.x = 25.0;
myView.frame = myFrame;
And I'm doing it all the time, but I don't know why I must do it that way. I would like to fill that gap in my understanding. Can someone explain ?
Nowadays Xcode gives you "Expression not assignable". Some time ago you got a compile error "Lvalue required as left operand of assignment".
There are two distinct dot syntaxes being used here. They look the same, but they do different things depending on what they are operating on and what is being done with it:
The first myView.frame is a shorthand for [myView frame], a method call that returns a CGRect struct by value.
myFrame.origin.x is accessing ordinary struct members in the traditional C fashion.
The second myView.frame is again a shorthand, but because the statement is an assignment it translates to calling a different method, [myView setFrame:myFrame].
In your single-line top example, you get a copy of the rect and set its x, but never copy it back to the view. You have to explicitly differentiate between the method calls, the dot syntax sugar can't magic them into a single call.
The reason this does not work is due to the mixing of two syntaxes.
First you have "." as a shortcut for calling the accessor functions (an Objective-C feature).
So
a.b becomes [a getB];
a.b = 5 becomes [a setB:5];
And then theres "." as direct struct member access (pure C). So
a.b really is a.b;
a.b really is a.b = 5;
Combining this in a set-value-case like this, doesn't work.
Because ...
If you could call
myView.frame.origin.x = 25.0;
The "myView.frame" part equals [myView getFrame] and you get a copied CGRect frame (a C struct)
The "myView.frame.origin" gives you a CGPoint origin (also a struct) of the copied CGRect
The "myView.frame.origin.x = 25.0" gives you a CGFloat x of the origin and now you want to assign something to it and here comes the problem...
You try to set a variable of a struct of a struct, which is ok, but there is no pointer from the UIView to the struct, so it is copied instead. So you copy and then you set and then you expect that the set action is somehow forwarded through the initial get to the UIView, well and this just doesn't work.
Of course you could wonder why Apple hasn't just created a shortcut, so that in the end your copied frame is automatically reinjected into a auto appended setFrame call, I guess you just have to live with how it is.
So remember, it would work if you'd get a pointer to the frame, but you don't, you get a copied struct instead.
So if you expect myView.frame.origin.x = 25.0 to work you indirectly expect your call to be automagically translated into some sort of
[myView setFrame:[myView getFrame:frame].origin.x = 25.0].
Well I guess you can admit that this looks wrong.
Also imagine if you'd get a direct pointer to the CGRect frame and you'd change something through that pointer, how would the UIView know that it's size has changed and that it has to update itself ? If on the other hand a [myView setFrame:newFrame] call is made, then UIView can do all the necessary readjusting itself.
A CGRect is a struct, which is something from standard C. A CGRect is not an Objective C object, so when you assign to one of its members, no setter method is called. Without a setter method being called, UIKit will not be able to know that anything has changed, and so will not be able to update the screen display.
Edit: as has been pointed out, the assignment will be to a copy of the struct.
When you manipulate the data directly, no accessor is called, so the UI cannot update itself - or inform any other component that wants to know about changes.
Edit: As pointed out by walkytalky, you will get a copy of the data, so changing it doesn't have any effect on the original anyway. The following example will show this:
UIView *aView = [[UIView alloc] initWithFrame:CGRectMake(50,50,100,100)];
NSLog(#"%f", aView.frame.origin.x); // will give 50
aView.frame.origin.x = 17; // operates on a copy of the rect only
NSLog(#"%f", aView.frame.origin.x); // will still give 50

Immutable alternative to CGPoint?

As the question states, I would prefer an immutable version of CGPoint, CGSize, and CGRect, although I would rather use a type which is supported by the iOS framework, to avoid converting between types.
So, is there any supported immutable version of CGPoint? If not, any recommendations?
Thanks!
You can just use constant CGPoints etc.
E.g.:
const CGSize size = (CGSize){6.0f, 4.0f};
Using CGSizeMake won't work as they are not compile time constants and you can't use variables, but this is the closest you'll get to an immutable CGSize. If that is not good enough then you'll need to use an object and convert when needed.
When a CGPoint (or any other struct - CGRect, etc.) is a property of an Objective-C object, the mechanics of getter/setter methods and the value semantics of C structs means that there is some level of protection against indirectly modifying the value of a CGPoint property of an object. For example, an expression like someObject.someCGPointProperty.x = 123 will not actually modify the x value of the CGPoint owned by someObject, because the someCGPointProperty accessor will return the CGPoint by value, which means that you are setting the x value on a copy of the CGPoint. To actually modify the CGPoint owned by that object, you would need to do something like:
CGPoint point = someObject.someCGPointProperty;
point.x = 123;
someObject.someCGPointProperty = point;
Regarding control over the number of instances you create: since CGPoint is a struct, it's passed around by value - effectively it's copied - in function arguments or Objective-C messages. So there's going to be plenty of copies of thees structs moving around anyway. However, these structs are created on the stack, not the heap, and a CGPoint is only 16 bytes (possibly smaller on 32-bit iOS devices?), so it's doubtful you need to worry about any performance implications.
Also, see #hypercrypt's more succinct answer regarding const values, as that may address your intentions well.
You can use the NSValue wrapper class. It can wrap a point, a size, a rect and more. It's immutable, so whatever you store inside it can't be changed (remember that if you store a pointer, the memory area to which points can still be changed).
Example
NSValue* value= [NSValue valueWithCGPoint: CGPointMake(x,y)];
If you try to set value.CGPointValue.x or y you get a compile error. The getter returns just a copy of the CGPoint held in the object, so there's no way to change it's value.

A bracket + dot syntax issue

I have been working with Objective C for a couple of months now and this has been my first time seeing this type of syntax in objective c. I think I know the equivalent of it but I wanted to be sure.
Specifically, the code looks like this
float imageHeight = [player texture].contentSize.height;
What is the '[player texture].contentSize.height equivalent to?
I know that the dot and bracket syntax can be the same thing
something.backgroundColor = [UIColor redColor];
[something setBackgroundColor:[UIColor redColor]];
The above two are pretty much the same..
So is this what's happening in my first example? I just have never seen a mix of bracket and dot syntax into one.
Thank's.
float imageHeight = [player texture].contentSize.height;
This is probably the same thing as:
float imageHeight = [[player texture] contentSize].height;
Dot syntax translates to a call of the appropriate property accessor method when it's applied to object pointers. When applied to structures, it's a direct access of one of the fields of that structure.
So, assuming that [player texture] returns a pointer to an object (probably an image), the first use of dot syntax accesses that object's contentSize property. If -contentSize also returns an object pointer, then the next use would also translate to a call of an accessor. More likely, though, the contentSize property is a NSSize, and NSSize is a structure with width and height fields. So the second use of dot syntax most likely retrieves the height field from that structure.
Mixing the two different meanings of dot syntax works fine for getting a value, but you'll get an error if you try to use it to set values. If my assumptions in the previous paragraph are correct, you can't do this:
[player texture].contentSize.height = 50;

Need help with CGRect & CGPoint for ios dev

Hey, I'm trying to implement double tap zooming and I'm trying to work with code from Apple's ScrollViewSuite example and several of the lines are giving me errors.
The first one is
tapLocation = midpointBetweenPoints(tapLocation, [touch locationInView:touch.view]);
And it says incompatible types in assignment. I haven't been able to find much information about midpointBetweenPoints other than that it compares two CGPoints and I believe that's what I'm passing it.
Second section that gives an error is
CGRect zoomRect = [self zoomRectForScale:newScale withCenter:tapPoint];
And it gives me invalid initializer.
Does anyone know what I'm doing wrong?
Both midpointBetweenPoints and zoomRectForScale:withCenter: are part of the example code rather than part of iOS itself.
midpointBetweenPoints returns a CGPoint (relevant to the error about assignment), but is not declared in a header anywhere. You'll see that in the sample in which it is used, 3_Tiling, it is defined at line 176 of TapDetectingView.m, declared at line 54 and used at 118 and 139. My guess would be that you're either not including the code in your project at all, or are including the definition but omitting the declaration. Objective-C is a superset of C, so follows the C rules. In C, any function for which the declaration cannot be found is assumed to return 'int'. Quite probably you need to add the declaration:
CGPoint midpointBetweenPoints(CGPoint a, CGPoint b);
Somewhere before your use.
At a guess, your use of zoomRectForScale:withCenter: is likely to be a similar issue. If declarations aren't found for Objective-C methods then they're assumed to return 'id', which is a pointer to a generic Objective-C object. CGRect is a C struct, so casting a pointer to it makes no sense. Assuming you've included the code from lines 401 to 416 of RootViewController.m, you also need to ensure the declaration is visible to the calling code. In the sample code, this is achieved on line 80 with the declaration:
#interface RootViewController (UtilityMethods)
- (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center;
#end
Declaring additions like that inside a source file is a way of approximating private methods in Objective-C. If you want it to be public, you can just add it to the interface declaration proper.

What's this {{0,0},{w,h}} doing?

I stumbled upon this piece of code today:
CGRect rect = {{0,0},{w,h}};
Here, I would have used a CGRectMake. But what does this thing in rambled brackets do? What kind of special-syntax is that? None of my objective-c books ever mentioned that.
It's a standard C structure initialization construct. Any structure can be initialized at declaration time by providing its contents in order within curly braces like this. Because a CGRect contains a CGPoint and a CGSize, you use one set of braces for the CGRect, then another set for each of CGPoint & CGSize.