Search code examples
iosobjective-cuiviewuiviewcontrollergetter-setter

Is using getters and setters the way to pass a message from a parent UIViewController to a method in a UIView class?


I am trying to reorganise an early project by separating code that belongs in a UIView from code that belongs in a UIViewController. Answers to a popular question (found here) don’t seem to address what I need to do so let me illustrate my question with two examples.

  • Example 1

Here the method setBackground:zone changes the background colour of a view to indicate various states in the app. The method as shown below currently works and I want to relocate the code to a view where it belongs.

ViewController.h

#import <UIKit/UIKit.h>
#import "CustomView.h"

@interface ViewController : UIViewController {
}
@end

ViewController.m

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    int zone                            = 1; // or 2 or 3;

   self.view                            = [[UIView alloc] initWithFrame: [UIScreen mainScreen].bounds];
    [self setBackground:zone];
}

- (void)setBackground:(int)zone {
    switch (zone) {
        case 1:
            self.view.backgroundColor   = [UIColor orangeColor];
            break;
        case 2:
            self.view.backgroundColor   = [UIColor cyanColor];
            break;
        case 3:
            self.view.backgroundColor   = [UIColor greenColor];
            break;
        default:
            break;
        }
    }
  • Example 2

In the code below I tried initialising background colour in CustomView by using getters and setters to reference the value of zone in ViewController (appropriate as ViewControllers in the original project already get and set zone to change background colour).

CustomView.h

#import <UIKit/UIKit.h>

@interface CustomView : UIView {
    UIViewController *parent;
    int selectedZone;
}
- (void)setParent:(UIViewController *)parent;
- (int)getSelectedZone;
@end

CustomView.m

#import "CustomView.h"

@implementation CustomView
    - (void)setParent:(UIViewController *)theParent {
        parent                          = theParent;
    }

    - (int)getSelectedZone {
        return selectedZone;
    }

    - (id)initWithFrame:(CGRect)frame 
        {
        self                            = [super initWithFrame:[UIScreen mainScreen].bounds];
        if (self) {

        NSLog(@"selectedZone in CustomView is seen as %i", [self getSelectedZone]);

            int zone                    = [self getSelectedZone];
            [self setBackground:(int) zone];
        }
        return self;
    }

- (void)setBackground:(int)zone {
    switch (zone) {
        case 1:
            self.view.backgroundColor   = [UIColor orangeColor];
            break;
        case 2:
            self.view.backgroundColor   = [UIColor cyanColor];
            break;
        case 3:
            self.view.backgroundColor   = [UIColor greenColor];
            break;
        default:
            break;
        }
    }

ViewController.h

#import <UIKit/UIKit.h>
#import "CustomView.h"

    @interface ViewController : UIViewController {    
        int selectedZone;
    }

    - (void)setSelectedZone:(int)zone;
    - (int)getSelectedZone;

ViewController.m

#import "ViewController.h"

@implementation ViewController

    - (void)viewDidLoad {
        [super viewDidLoad];

        int zone                        = 1; // or 2 or 3;    
        [self setSelectedZone:(int)zone];
        NSLog(@"selectedZone in ViewController is now set to %i", [self getSelectedZone]);

        self.view                       = [[UIView alloc] initWithFrame: [UIScreen mainScreen].bounds];

        CustomView *cv                  = [[CustomView alloc]init];
        [self.view addSubview:cv];
    }

    - (void)setSelectedZone:(int)zone {
        selectedZone                    = zone;
    }

    - (int)getSelectedZone {
        return selectedZone;
    }

I can tell my code above does not work because getSelected:zone in CustomView cannot reference zone set by setSelected:zone in the ViewController. But I don’t understand why.

2017-04-05 07:04:26.126 ZoneIndicator[1865:1270743] selectedZone in ViewController is now set to 1 
2017-04-05 07:04:26.126 ZoneIndicator[1865:1270743] selectedZone in CustomView is seen as 0

But an article found here has even made me question whether using getters and setters is the best approach - especially this:

The biggest danger here is that by asking for data from an object, you are only getting data. You’re not getting an object—not in the large sense. Even if the thing you received from a query is an object structurally (e.g., a String) it is no longer an object semantically. It no longer has any association with its owner object. Just because you got a string whose contents was “RED”, you can’t ask the string what that means. Is it the owners last name? The color of the car? The current condition of the tachometer? An object knows these things, data does not.

So how do I pass the message from a parent UIViewController to a method in a UIView class ?


Solution

  • It is perfectly okay for a vc to set it's view's properties. Here's annotated code for your first example...

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        // no need to do this, the UIViewController this inherits from creates the view
        // int zone                            = 1; // or 2 or 3;
        //self.view                            = [[UIView alloc] initWithFrame: [UIScreen mainScreen].bounds];
        [self setBackgroundColorForZone:zone];
    }
    
    // improved naming for clarity
    - (void) setBackgroundColorForZone:(NSInteger)zone {
        // fine as you have it
        switch //...
        // ...
    }
    

    If you have some better reason to develop a custom UIView subclass to be that view controller's view, that's okay, too. You can either replace your view with the custom instance or cover the default view with a child that has the same frame. But it's unwise to give a view a pointer to it's view controller (and even more unwise to call this pointer "parent")

    So for your second example, the custom view class should be substantially simplified...

    @interface CustomView : UIView {
        // commented out bad stuff
        // UIViewController *parent;
        // this isn't needed either
        //int selectedZone;
    }
    // these aren't needed either
    //- (void)setParent:(UIViewController *)parent;
    //- (int)getSelectedZone;
    
    // just this
    - (void)setBackgroundColorForZone:(NSInteger)zone;
    
    @end
    

    And the implementation can have the same method that was in your first example. It takes a zone integer and set's self.backgroundColor (instead of self.view.backgroundColor).

    The view controller that manages this view can be simplified now to:

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        int zone                        = 1; // or 2 or 3; 
        // don't need this   
        //[self setSelectedZone:(int)zone];
        //NSLog(@"selectedZone in ViewController is now set to %i", [self getSelectedZone]);
    
        // never need this
        //self.view                       = [[UIView alloc] initWithFrame: [UIScreen mainScreen].bounds];
    
        // notice the change to init with frame
        CustomView *cv                  = [[CustomView alloc]initWithFrame:self.view.bounds];
        [cv setBackgroundColorForZone:zone];
        [self.view addSubview:cv];
    }
    
    // don't need any of this
    //- (void)setSelectedZone:(int)zone {
    //    selectedZone                    = zone;
    //}
    
    //- (int)getSelectedZone {
    //    return selectedZone;
    //}