Layout Managers for iOS Views

The purpose of this blog post is to demonstrate a different approach from the Springs and Struts or Auto Layout models for view layout in an iOS application..

Torey Lomenda

The purpose of this blog post is to demonstrate a different approach from the Springs and Struts or Auto Layout models for laying out views in an iOS application.  It is not meant to compare the pros and cons of each approach, but rather to highlight how Layout Managers, implemented in Objective-C, can be used to layout views in a more encapsulated, intuitive way.

The pre-iOS 6 Springs and Struts model automatically adjusts size and position by using autosizing masks that control a view’s margins (struts) and size (springs - width and height) when its superview changes.  This works fine for simple user interfaces (UIs) but it gets harder to work with as your UI becomes more complex.

To help with more intricate UI layouts - especially on the iPad - Apple has developed something called Auto Layout to help developers in iOS 6.  As described by Apple: “…With auto layout, you define rules for how to lay out the elements in your user interface. These rules express a larger class of relationships and are more intuitive to use than springs and struts…“.  For more info on auto layout go to About Cocoa Auto Layout.

All that is very nice, and I am sure I will take advantage of auto layout in the future.  However, coming from the Web (HTML, JavaScript, CSS) and Java worlds I find the CSS Box Model and Java Layout Managers a more natural way to think of laying out UI views.  This motivated me to develop a hybrid approach for laying out iOS views using Objective-C.  It is also worth mentioning that at the time I developed this approach iOS support for Auto Layout and NSCollectionView did not exist as options.

The rest of this blog post sets out to describe the native Objective-C implementation I developed to support various Layout Managers.  I have found this approach to be useful over the last few years for laying out sophisticated UIs in iPad apps.  They have served me well for apps I have developed, such as the CarSoup iPad app available on the App Store.  One more point of interest.  At the time of writing there are a number of alternatives for laying out iOS views.  You may also be interested in checking out:

Let’s Get Started

Below provides an explanation and some code snippets for various layouts supported.  The source code and XCode Project are available for download from this blog post.  All of the code snippets you see below can be found in the packaged source code and XCode project.  The source is available to you under the MIT license.  Use as you wish, but it is made available as-is for demonstration purposes only.

Source Code and XCode Project

Enabling Layouts in Your Custom UI View Classes

Below are the basic steps to setup a layout manager for your Custom UI View.

Define a layout property on your UIView class.  Any view or subview can have a layout property defined.  iOS already has a built in lifecycle for laying out views (ie:  layoutSubviews) that gets invoked in a chain-like fashion to layout your view hierarchy.  Within your view class:

#import "AbstractLayout.h"  

@property (nonatomic, strong) AbstractLayout *layout;

Setup the Layout instance (See samples below)

In layoutSubviews method of your UIView class add the layout logic.

- (void) layoutSubviews {
    [super layoutSubviews];  

    // Layout Manager Code
    [layout layoutForView: self];
}

*Note, you can apply the layout manager to any view within the hierarchy.  Also, you can specify different layouts for your custom UIView classes to further nest layouts in an encapsulated manner.

How About Some Examples

First let’s set the stage.  To further understand the code snippets and screen shots in this article imagine you have added four views to your custom UI View (these samples use ARC).

PlaceHolderView *view1 = [[PlaceHolderView alloc] initWithFrame:CGRectZero];
PlaceHolderView *view2 = [[PlaceHolderView alloc] initWithFrame:CGRectZero];
PlaceHolderView *view3 = [[PlaceHolderView alloc] initWithFrame:CGRectZero];
PlaceHolderView *view4 = [[PlaceHolderView alloc] initWithFrame:CGRectZero];  

[view1 setPlaceHolderItem:@"View 1"];
 [view2 setPlaceHolderItem:@"View 2"];
[view3 setPlaceHolderItem:@"View 3"];
[view4 setPlaceHolderItem:@"View 4"];  

[self addView: view1];
[self addView: view2];
[self addView: view3];
[self addView: view4];

Okay, on to the examples.  All layout implementations allow for margins and padding, as well as vertical and horizontal alignment properties similar to the CSS Box Model. With the code snippets below, hopefully you will see that this approach can be intuitive enough to layout a number of views automatically (if you are comfortable with Objective-C).  They have saved me many hours of work.

Fill Layout

A fill layout is used when you want your view to adjust its height and width dynamically as its superview is resized.  Fill layouts work great if you have an array views that you want to overlay on top of each other and bring a specific view to the front based on user gestures.  This would be similar to a “card” layout where only one view is “visible” at a time.  The snippet below configures a fill layout manager with a margin of 10 pixels (top, right, bottom, left) and a padding of 20 pixels (top, right, bottom, left) to space out the views.

self.layout = [FillLayout margin:@"10px 10px 10px 10px" padding:@"20px 20px 20px 20px" valign:nil halign:nil items:
                         [ViewItem viewItemFor:view1],
                         [ViewItem viewItemFor:view2],
                         [ViewItem viewItemFor:view3],
                         [ViewItem viewItemFor:view4],
                         nil];

[caption id="" align=“aligncenter” width=“512” caption=“Fill Layout Sample”][/caption]

Vertical Layout

A vertical layout is used to layout your views up and down evenly spaced from each other.  The snippet below configures a vertical layout manager with a margin of 10 pixels (top, right, bottom, left) and a padding of 10 pixels (top, right, bottom, left) to space out the views.

self.layout = [VerticalLayout margin:@"10px 10px 10px 10px" padding:@"10px 10px 10px 10px" valign:nil halign:nil items:
                         [ViewItem viewItemFor:view1 width:@"300px" height:@"150px"],
                         [ViewItem viewItemFor:view2 width:@"100px" height:@"150px"],
                         [ViewItem viewItemFor:view3 width:@"100px" height:@"150px"],
                         [ViewItem viewItemFor:view4 width:@"100px" height:@"150px"],
                         nil];

[caption id="" align=“aligncenter” width=“512” caption=“Vertical Layout Sample”][/caption]

Horizontal Layout

A horizontal layout is used to layout your views left to right evenly spaced from each other.  The snippet below configures a fill horizontal manager with a margin of 10 pixels (top, right, bottom, left) and a padding of 10 pixels to the left of each view to space out the views.

self.layout = [HorizontalLayout margin:@"10px" padding:@"0px 0px 0px 10px" valign:@"center" halign:@"center" items:
                        [ViewItem viewItemFor:view1 width:@"100px" height:@"100px"],
                        [ViewItem viewItemFor:view2 width:@"100px" height:@"300px"],
                        [ViewItem viewItemFor:view3 width:@"100px" height:@"400"],
                        [ViewItem viewItemFor:view4 width:@"100px" height:@"100px"],
                        nil];

[caption id="" align=“aligncenter” width=“512” caption=“Horizontal Layout Sample”][/caption]

Tile Layout

A tile layout is used to layout your views in a tiled fashion (rows and columns) with all viewshaving the same height and width.  Height and width adjusts based on the number of rows and columns for the tiled layout.  The snippet below configures a tiled layout manager with a margin of 10 pixels (top, right, bottom, left) and a padding of 20 pixels (top, right, bottom, left) to space out the views.

self.layout = [TileLayout margin:@"10px 10px 10px 10px" padding:@"20px 20px 20px 20px" valign:nil halign:nil items:
                         [ViewItem viewItemFor:view1],
                         [ViewItem viewItemFor:view2],
                         [ViewItem viewItemFor:view3],
                         [ViewItem viewItemFor:view4],
                         nil];
// Define the Tiled Grid
self.layout.rows = 2;
self.layout.cols = 2;

[caption id="" align=“aligncenter” width=“512” caption=“Tiled Layout Sample”][/caption]

Container Layouts: Grids

Grid layouts are the most flexible way to layout an intricate UI.  Here are a few examples of setting up a grid of rows and cells, combined with some of the standard layouts described above.

Grid Layout Sample 1 The grid layout below defines a grid with 3 rows, each that are 1/3 the height of its superview.  A few other points about the layout:

  • The first row has one cell with its containing views laid out horizontally.
  • The second row has 2 cells each taking 50% of the row’s width.
  • The first cell is an empty cell,and the 2nd cell uses a fill layout for the view.
  • The third row defines 3 equally width cells (2 empty, the middle one with a view laid out horizontally).
  • Margins, paddings and alignment properties can be “inherited” by cell layouts or can define there own properties.
self.layout = [GridLayout margin:@"10px" padding:@"5px" valign:@"center" halign:@"center" rows:
                           [GridRow height:@"33%" cells:
                            [GridCell width:@"100%" horizontalLayoutFor:[ViewItem viewItemFor:view1 width:@"100px" height:@"100%"], nil],
                            nil],  

                           [GridRow height:@"33%" cells:
                            [GridCell width:@"50%" useGridLayoutProps:YES layout:nil],
                            [GridCell width:@"50%" useGridLayoutProps:YES layout:
                             [FillLayout items:
                              [ViewItem viewItemFor:view2], nil]], nil],  

                           [GridRow height:@"34%" cells:
                            [GridCell width:@"33%" horizontalLayoutFor:[ViewItem viewItemFor:view3 width:@"100px" height:@"100"], nil],
                            [GridCell width:@"33%" useGridLayoutProps:YES layout:nil],
                            [GridCell width:@"34%" layout:
                            	[HorizontalLayout margin:@"0px" padding:@"0px" valign:@"center" halign:@"center"
                                	items:[ViewItem viewItemFor:view4 width:@"100px" height:@"100px"]
                           nil];

[caption id="" align=“aligncenter” width=“512” caption=“Grid Layout Sample 1”][/caption]

Grid Layout Sample 2

This grid sample demonstrates the ability to have grids within grids (nested grids).

self.layout = [GridLayout margin:@"10px" padding:@"0px" valign:@"center" halign:@"center" rows:
                           [GridRow height:@"100%" cells:
                            [GridCell width:@"50%" useGridLayoutProps:YES layout:
                             [GridLayout rows:
                              [GridRow height:@"50%" cells:
                               [GridCell width:@"100%" horizontalLayoutFor:[ViewItem viewItemFor:view1 width:@"100" height:@"100px"], nil],
                               nil],
                              [GridRow height:@"50%" cells:
                               [GridCell width:@"50%" horizontalLayoutFor:[ViewItem viewItemFor:view2 width:@"100" height:@"100px"], nil],
                               [GridCell width:@"50%" horizontalLayoutFor:[ViewItem viewItemFor:view3 width:@"100" height:@"100px"], nil],
                               nil],  

                              nil]],
                            [GridCell width:@"50%" useGridLayoutProps:YES layout:
                             [FillLayout items:[ViewItem viewItemFor: view4], nil]],
                            nil],
                           nil];

[caption id="" align=“aligncenter” width=“512” caption=“Grid Layout Sample 2”][/caption]

Container Layouts: Border Layout

Border layout separates the screen real estate into north, south, east, and west regions.  Folks familiar with Java Swing (snicker…snicker) understand this type of layout.  This layout works great when laying out your view with headers, footers, etc.

Border Layout Sample 1

The layout below has:

  • North (top) region that takes up 10% of the height of its superview
  • South (bottom) region that is 190 pixels high
  • West (left) region that is 150 pixels wide
  • A center region that takes up the rest of the real estate.

As mentioned above, region layouts can inherit properties or define their own properties.

self.layout = [BorderLayout margin:@"10px" padding:@"5px"
                                         north:[BorderRegion size:@"10%" useBorderLayoutProps:YES layout:
                                                [FillLayout items:[ViewItem viewItemFor:view1], nil]]
                                         south:[BorderRegion size:@"190px" useBorderLayoutProps:YES layout:
                                                [FillLayout items:[ViewItem viewItemFor:view2], nil]]
                                          east: nil
                                          west: [BorderRegion size:@"150px" useBorderLayoutProps:YES layout:
                                                 [FillLayout items:[ViewItem viewItemFor:view3], nil]]
                                        center:[BorderRegion layout:
                                                [HorizontalLayout margin:@"0px" padding:@"0px" valign:@"center" halign:@"center"
                                                                   items:[ViewItem viewItemFor:view4 width:@"100px" height:@"100px"], nil]]];

[caption id="" align=“aligncenter” width=“512” caption=“Border Layout Sample 1”][/caption]

Border Layout Sample 2

The layout below has:

  • North (top) region that takes up 10% of the height of its superview
  • South (bottom) region that is 190 pixels high
  • West (left) region that is 150 pixels wide
  • East (right) region that is 150 pixels wide
  • There is not defined center region for this layout
self.layout = [BorderLayout margin:@"10px" padding:@"5px"
                                         north:[BorderRegion size:@"10%" useBorderLayoutProps:YES layout:
                                                [FillLayout items:[ViewItem viewItemFor:view1], nil]]
                                         south:[BorderRegion size:@"190px" useBorderLayoutProps:YES layout:
                                                [FillLayout items:[ViewItem viewItemFor:view2], nil]]
                                          east: [BorderRegion size:@"150px" useBorderLayoutProps:YES layout:
                                                 [FillLayout items:[ViewItem viewItemFor:view3], nil]]
                                          west: [BorderRegion size:@"150px" useBorderLayoutProps:YES layout:
                                                 [FillLayout items:[ViewItem viewItemFor:view4], nil]]
                                        center:nil];

[caption id="" align=“aligncenter” width=“512” caption=“Border Layout Sample 2”][/caption]

In Closing

I hope you enjoyed reviewing these layout samples for iOS using this approach.  Feel free to download the source and look at how these layout managers are implemented.  I am not saying you should follow the same approach, but it has helped my collegues and I get a handle on UI layouts, especially for iPad apps.

Share this Post

Related Blog Posts

Mobile

Using Google Analytics iOS SDK in Shared Library Projects

January 3rd, 2013

iOS projects that utilize shared libraries for common components can be difficult to instrument with Google Analytics. This is one approach to make it easier.

Steve McCoole
Mobile

Start building out automated groovy mobile web application testing on your iPhone or iPad with Ge...

April 24th, 2012

Geb and Spock can be used to automate testing of mobile application on mobile devices, including the iPhone, iPad and Android devices.

Object Partners
Mobile

Leverage Spring Security to Handle Mobile Access to Your Grails App

March 28th, 2012

Add the ability to detect and redirect mobile devices to specific version of your grails app by using / configuring the spring security plugin that many grails apps already use.

Nick Larson

About the author

Torey Lomenda

Chief Software Technologist

Torey is a Chief Technologist at Object Partners Inc. specializing in Mobile & Enterprise technologies with over 19 years of professional experience. Most recently he has led a number of iOS-related projects, applying his expertise in building HTML5 and Native Objective-C & Swift-based apps for iOS (iPad/iPhone). He has led the development of various mission-critical applications and supports a pragmatic delivery approach using various agile methodologies. During his career he has gained expertise with iOS, Java/JEE ecosystem and its related open source (ie: Spring-related, Tomcat), Groovy/Grails, JavaScript & technologies (ExtJS, AngularJS) to deliver rich internet applications (RIAs), and various commercial technologies (ie: IBM/Tivoli, Oracle, Tibco).