Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to gain assignment access to CGRect elements when the CGRect is an instance variable

@interface some_class : some_parent {

 }
@property (nonatomic, assign) CGRect the_rect;

@end

...

@implementation some_class

@synthesize the_rect;

@end

After creating an instance of some_class:

instance_of_some_class.the_rect.origin.x = 0.0;

Which generates an "Expression not assignable" error.

Of course this works fine:

instance_of_some_class.the_rect = CGRectMake(0,0,0,0);

I guess the problem is that the auto-magically created setter only knows about assigning a CGRect to "the_rect". OK, I get that. Can I tell it to not create a setter so that I can access the member variable directly and make assignments to the struct members without having to assign the entire CGRect?

I suppose that I could break this out into four individual CGFloats for origin.x, origin.y, size.width and size.height...but you'd think there would be a way to do this with CGRects instead.

What's confusing is that this, of course, works fine:

CGRect test = instance_of_some_class.the_rect.origin.x;

In other words, the getter knows how to navigate the struct and pull out the value of one of its elements so I can use it. The opposite does not seem to be the case as you just can't reach into an ivar that happens to be a struct and make an assignment to one of its elements without having to assign the entire struct.

I also tried this:

@interface some_class : some_parent {

@public

    CGRect the_rect;

 }
@property (nonatomic, assign) CGRect the_rect;

@end

After instantiating in another module:

instance.the_rect->origin.x = some_value;

...and got an error saying that the member being referenced is not a pointer. I tried taking the address of instance.the_rect ... but that didn't work either.

EDIT TO CLARIFY: This is about creating a class that has some ivars that happen to be structs. Then, after instantiating the class somewhere else, you want to be able to make assignments to the struct ivar elements directly:

class_instance.struct_ivar.element = something;

I am using CGRect as a convenient example of a well-known struct that one might want to use as an ivar.

like image 901
martin's Avatar asked May 02 '11 18:05

martin's


3 Answers

So you know how to get around your problem, but here's why you have to do so and one of the reasons why dot syntax in Objective-C is an unholy perversion:

anInstance.property = blah is compiled to the exact same code that would be generated if you had instead typed: [anInstance setProperty:blah]

This works fine if your properties are only object properties. Unfortunately for us, dot syntax already has an unrelated meaning in C, and that's to access members of a struct. ie, you can do:

NSRect foo;
foo.origin.x = 42;

If foo were an object, then foo.origin.x = 42 would be the same as [[foo origin] setX:42] (assuming origin were also an object). However, since they're simply structs, it's just doing an offset calculation and setting the value in memory directly. There's no method invocation going on.

Enter dot syntax in Objective-C. Now we have two different meanings of the dot operator (something that not even C++ allows, ironically enough, with all of its operator overloading). Sometimes you're using it to access a struct member. Sometimes you're using it to invoke an object accessor. Sometimes you can mix-and-match those uses. Sometimes you can't.

You can mix-and-match member vs accessor access if the expression is being used as an rvalue. In other words, you can do:

foo = anObject.frame.origin.x;

However, if you try to do this as an lvalue, it fails:

anObject.frame.origin.x = foo;

The way to work around this is to do:

NSRect frame = anObject.frame;
frame.origin.x = 42;
anObject.frame = frame;

Of course, if you just forgot that dot syntax existed altogether, then you'd never have this problem, because trying to coming up with a way to do this using bracket syntax would be non-sensical.

And this is one of the many reason why I think dot syntax was a terrible mistake. You never would've been confused about this if it had never been added.

like image 172
Dave DeLong Avatar answered Nov 24 '22 17:11

Dave DeLong


You don't generally access member variables directly from outside the class. You can declare it as @public if you really want to. I think you may have to create accessors for each of the rect's members:

- (void)setTheRectX:(CGFloat)newX {
    the_rect.origin.x = newX;
}

- (void)setTheRectY:(CGFloat)newY {
    the_rect.origin.y = newY;
}

- (void)setTheRectOrigin:(CGPoint)newOrigin {
    the_rect.origin = newOrigin;
}

and so on.

The issue is that when you ask for the instance's the_rect, you don't get a pointer to the same rect that the instance has, like you would if the ivar was an object pointer -- you get a new copy of the struct.

There are a few questions floating around SO that discuss this issue: 1 2 3

Public ivar:

@interface RectHolder : NSObject {

@public
    CGRect the_rect;


}

Access:

RectHolder * myRH = [[RectHolder alloc] init];
myRH->the_rect = CGRectMake(0.0, 0.0, 100, 100);

myRH->the_rect.origin.x = 10;

NSLog(@"%@", NSStringFromRect(myRH->the_rect));
like image 40
jscs Avatar answered Nov 24 '22 19:11

jscs


You can't assign to elements of a struct. This is what you must do instead:

CGRect rect = object.rect;
rect.origin.x = 3.0f;
rect.size.height = 53.3f;
object.rect = rect;

By doing this you aren't assigning to the rect's elements, but rather changing the entire rect value instead.

like image 44
Alexsander Akers Avatar answered Nov 24 '22 18:11

Alexsander Akers