Search code examples
iosobjective-csetneedsdisplayuiview-hierarchy

iOS: [self.view setNeedsDisplay];


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?


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.