Matthew Morey

I'm an engineer, developer, author, hacker, creator, tinkerer, traveler, snowboarder, surfer, and husband.

I create iOS apps professionally and independently.

Creating UIViews programmatically with Auto Layout

12 October 2013

Since Xcode 4.0 I have exclusively developed views in my iOS apps with Interface Builder (IB). First with xibs and recently with Storyboards. I'm a fan of IB and have successfully released several apps that use Auto Layout in the often criticized Xcode 4.x version of IB. I have been able to avoid the most common IB compliant, merge issues, with good team communication and a bit of luck.

With a reliance on IB it is easy to forget how to create views programmatically. Even if you remember, how would you do it with Auto Layout?

First place to look is the Creating a View Programmatically section of the View Controller Programming Guide for iOS.

If you prefer to create views programmatically, instead of using a storyboard, you do so by overriding your view controller’s loadView method. Your implementation of this method should do the following:

  1. Create a root view object.

    The root view contains all other views associated with your view controller. You typically define the frame for this view to match the size of the app window, which itself should fill the screen. However, the frame is adjusted based on how your view controller is displayed. See “Resizing the View Controller’s Views.”

    You can use a generic UIView object, a custom view you define, or any other view that can scale to fill the screen.

  2. Create additional subviews and add them to the root view.

    For each view, you should:

    1. Create and initialize the view.

    2. Add the view to a parent view using the addSubview: method.

  3. If you are using auto layout, assign sufficient constraints to each of the views you just created to control the position and size of your views. Otherwise, implement the viewWillLayoutSubviews and viewDidLayoutSubviews methods to adjust the frames of the subviews in the view hierarchy. See “Resizing the View Controller’s Views.”

  4. Assign the root view to the view property of your view controller.

Apple gives us four concise steps to follow and even a code example:

- (void)loadView
{
    CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
    UIView *contentView = [[UIView alloc] initWithFrame:applicationFrame];
    contentView.backgroundColor = [UIColor blackColor];
    self.view = contentView;

    levelView = [[LevelView alloc] initWithFrame:applicationFrame viewController:self];
    [self.view addSubview:levelView];
}

I'm not sure why they are passing the view controller as a parameter to LevelView. Any ideas?

Seems simple enough, just override the loadView method and set the view property with the programmatically created view. If you want to use Auto Layout just add constraints instead of setting the frames. For example, to produce a red box half the size of the screen that is centered both horizontally and vertically you add four constraints.

// MDMViewController.m
- (void)loadView {

    UIView *contentView = [[UIView alloc] init];
    contentView.backgroundColor = [UIColor greenColor];
    self.view = contentView;

    UIView *centerView = [[UIView alloc] init];
    [centerView setTranslatesAutoresizingMaskIntoConstraints:NO];
    centerView.backgroundColor = [UIColor redColor];
    [self.view addSubview:centerView];

    // Width constraint, half of parent view width
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView
                                                          attribute:NSLayoutAttributeWidth
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeWidth
                                                         multiplier:0.5
                                                           constant:0]];

    // Height constraint, half of parent view height
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView
                                                          attribute:NSLayoutAttributeHeight
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeHeight
                                                         multiplier:0.5
                                                           constant:0]];

    // Center horizontally
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView
                                                          attribute:NSLayoutAttributeCenterX
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeCenterX
                                                         multiplier:1.0
                                                           constant:0.0]];

    // Center vertically
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView
                                                          attribute:NSLayoutAttributeCenterY
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeCenterY
                                                         multiplier:1.0
                                                           constant:0.0]];

}
iPhone 5 screenshot showing a centered view inside of another view iPhone 4 screenshot showing a centered view inside of another view

With only a single subview it is easy to see how bloated your controller can become using this technique. Following the Model-View-Controller (MVC) design pattern we should really move this code from the controller to the view.

// MDMViewController.m
- (void)loadView {

    self.view = [[MDMView alloc] initWithFrame:CGRectZero];

}
// MDMView.m
- (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) {

        // Initialization code
        self.backgroundColor = [UIColor greenColor];

        UIView *centerView = [[UIView alloc] init];
        [centerView setTranslatesAutoresizingMaskIntoConstraints:NO];
        centerView.backgroundColor = [UIColor redColor];
        [self addSubview:centerView];

        // Width constraint
        [self addConstraint:[NSLayoutConstraint constraintWithItem:centerView
                                                         attribute:NSLayoutAttributeWidth
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:self
                                                         attribute:NSLayoutAttributeWidth
                                                        multiplier:0.5
                                                          constant:0]];

        // Height constraint
        [self addConstraint:[NSLayoutConstraint constraintWithItem:centerView
                                                         attribute:NSLayoutAttributeHeight
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:self
                                                         attribute:NSLayoutAttributeHeight
                                                        multiplier:0.5
                                                          constant:0]];

        // Center horizontally
        [self addConstraint:[NSLayoutConstraint constraintWithItem:centerView
                                                         attribute:NSLayoutAttributeCenterX
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:self
                                                         attribute:NSLayoutAttributeCenterX
                                                        multiplier:1.0
                                                          constant:0.0]];

        // Center vertically
        [self addConstraint:[NSLayoutConstraint constraintWithItem:centerView
                                                         attribute:NSLayoutAttributeCenterY
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:self
                                                         attribute:NSLayoutAttributeCenterY
                                                        multiplier:1.0
                                                          constant:0.0]];

    }
    return self;
}

Now there is proper separation between the controller and view but it seems weird calling initWithFrame: method with CGRectZero to initialize a full screen view. This can be cleaned up a little more by overriding the init method and compartmentalizing the view initialization code.

// MDMViewController.m
- (void)loadView {

    self.view = [[MDMView alloc] init];

}
// MDMView.m
- (id)init {

    return [self initWithFrame:CGRectZero];

}

- (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) {

        // Initialization code
        self.backgroundColor = [UIColor greenColor];
        [self addSubview:self.centerView];
        [self setupConstraints];

    }
    return self;
}

- (UIView *)centerView {

    if (_centerView == nil) {
        _centerView = [[UIView alloc] init];
        [_centerView setTranslatesAutoresizingMaskIntoConstraints:NO];
        _centerView.backgroundColor = [UIColor redColor];
    }
    return _centerView;

}

- (void)setupConstraints {

    // Width constraint
    [self addConstraint:[NSLayoutConstraint constraintWithItem:self.centerView
                                                     attribute:NSLayoutAttributeWidth
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:self
                                                     attribute:NSLayoutAttributeWidth
                                                    multiplier:0.5
                                                      constant:0]];

    // Height constraint
    [self addConstraint:[NSLayoutConstraint constraintWithItem:self.centerView
                                                     attribute:NSLayoutAttributeHeight
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:self
                                                     attribute:NSLayoutAttributeHeight
                                                    multiplier:0.5
                                                      constant:0]];

    // Center horizontally
    [self addConstraint:[NSLayoutConstraint constraintWithItem:self.centerView
                                                     attribute:NSLayoutAttributeCenterX
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:self
                                                     attribute:NSLayoutAttributeCenterX
                                                    multiplier:1.0
                                                      constant:0.0]];

    // Center vertically
    [self addConstraint:[NSLayoutConstraint constraintWithItem:self.centerView
                                                     attribute:NSLayoutAttributeCenterY
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:self
                                                     attribute:NSLayoutAttributeCenterY
                                                    multiplier:1.0
                                                      constant:0.0]];
}

Reading the docs on UIView subclassing leads to the updateConstraints method which implies custom views with constraints should override this method.

// MDMView.m
- (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) {

        // Initialization code
        self.backgroundColor = [UIColor greenColor];
        [self addSubview:self.centerView];
        [self setNeedsUpdateConstraints];

    }
    return self;
}

- (void)updateConstraints {

    [self setupConstraints];
    [super updateConstraints];

}

For updateConstraints to be called the first time you need to call setNeedsUpdateConstraints at the end of the initialization method.

Now there is a potential problem because updateConstraints may be called more than once and you don't want to add the same constraints multiple times. Really updateConstraints should just be used for code that updates existing constraints such as the constant property. To fix this issue you could wrap the initial constraint setup code in a status variable check.

// MDMView.m
- (void)updateConstraints {

    if (self.didSetupConstraints == NO){
        [self setupConstraints];
    }
    [super updateConstraints];

}

I prefer not to use instance variable or properties to hold object states as it has a tendency to make code confusing and bug prone. In this simple example it is better to just setup the constraints via the initialization method.

Xcode 5 brings many improvements to IB including better support for Auto Layout. In most cases you should use IB. If you find yourself looking for an example on how to do it programmatically here is the the full source code. If I missed something please let me know.