Question

I seem to have forgotten the basics regarding this or perhaps I am just too tired.

I have a view controller and a view that is a subclass of UIView that is named MyViewClass.

In viewDidLoad of my viewController I alloc and init self.myView like this:

self.myView = [[MyViewClass alloc] initWithFrame:(CGRect){{0, 0}, 320, 480}];
self.view = self.myView;

At some time in my viewController, I would call [self.view setNeedsDisplay];. No problem here.

I expose a property in MyViewClass.h:

@property(nonatomic) i;

The property i will be used in drawRect in myView; So, from my viewController I would set it like so:

self.myView.i = 100;

So my question is:

Why am I able to call [self.view setNeedsDisplay]; in my view controller without needing to call [self.myView setNeedsDisplay];and drawRect will be called in myView, but I can't just do self.view.i in my view controller as self.myView has been assigned to self.view already in viewDidLoad in my view controller?

Was it helpful?

Solution

You are able to call [self.view setNeedsDisplay], but not self.view.i because UIViewController's view property is of type UIView.

self.myView has been assigned to self.view already in viewDidLoad

Remember that the actual assignment happens at runtime, while compiler errors are generated at compile time. The compiler has no idea in what order your view controller's methods may be called, so it can't make any guesses about the actual type of a property's value beyond the property's original declaration. As far as Xcode is concerned, self.view is just a UIView.

As danh suggested above, you can override the property declaration to inform the compiler that your view is of type MyView. Although, your approach of using the self.myView property may be preferred anyway: it allows you to change up the view hierarchy in the future without changing your interface, and doesn't muck with inherited methods. UITableViewController does the same thing: both the view and tableView properties return the same view instance, but the properties are typed differently.

It's important to remember that the type of a property is not necessarily the same as the type of its value. self.view may have been assigned to an instance of MyView, but the property is still of type UIView. Regardless of the type of object assigned to it, the property's accessor is still a method with return type UIView.

So, as far as the compiler is concerned, self.view is nothing more than a plain old UIView, even if you know that it isn't.

I think it would help to elaborate more on what properties are, and differentiate between the property and the instance. When you create a property like this:

@interface MyViewController : UIViewController

@property(strong, nonatomic) MyView *myView;

@end

The compiler translates it into a variable, an accessor method (the "getter"), and a mutator method (the "setter"). The property is merely shorthand for declaring the methods like so:

@interface MyViewController : UIViewController
{
    MyView *_myView;
}

- (MyView *)myView; // accessor / getter
- (void)setMyView:(MyView *)myView; // mutator / setter

@end

@implementation MyViewController

- (MyView *)myView
{
    return _myView;
}

- (void)setMyView:(MyView *)myView
{
    _myView = myView;
}

@end

When you call self.myView, or self.view, you are actually calling the accessor method; it's equivalent to [self myView] or [self view]. What it returns is a pointer to an object in memory. Because you assigned self.view = self.myView, both properties are set to the same object; thus, both self.view and self.myView return a pointer to the same object.

To summarize:

  • Assigning self.view = self.myView generates no compiler error, because MyView is a subclass of UIView. Note that assigning self.myView = self.view would generate a warning because UIView is not a subclass of MyView
  • Calling [self.view setNeedsDisplay] causes myView to draw itself, because the same instance is assigned to both properties. If you log the descriptions (NSLog(@"view=%@, myView=%@", self.view, self.myView)) for both properties, you can observe that they have the same memory address
  • You cannot call self.view.i because the view property is declared to have type UIView, and UIView has no method named i.

OTHER TIPS

In viewDidLoad of my viewController I alloc and init self.myView like this:

self.myView = [[MyViewClass alloc] initWithFrame:(CGRect){{0, 0}, 320, 480}];
self.view = self.myView;

Never never never do that in viewDidLoad. viewDidLoad means you already have a view. You should never ever change your view controller's view.

It is fine (and required) to set your view loadView. That is what it is for. But in viewDidLoad, absolutely not.

You're trying to substitute the VCs view with a UIView subclass, have it work in all the usual ways, but also give the vc's code access to properties on the subclass? I think you can get there by overriding the view getter:

- (MyView *)view {
    return (MyView *)[super view];
}

Also, you can setup the custom view in IB as the VC's view, so there's no need to do anything in loadView.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top