Mar 15, 2013

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 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:

  • NSCollectionView a built-in iOS component that displays an array of content into a grid of views.
  • AQGridView – https://github.com/AlanQuatermain/AQGridView
  • WeViews – http://charlesmchen.github.com/WeViews/index.html

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];

Fill Layout Sample

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];

Vertical Layout Sample

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];

Horizontal Layout Sample

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;

Tiled Layout Sample

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];

Grid Layout Sample 1

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];

Grid Layout Sample 2

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]]];

Border Layout Sample 1

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];

Border Layout Sample 2

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.

About the Author

Torey Lomenda profile.

Torey Lomenda

VP - Solution Delivery

Torey has over 26 years of software development experience.  As VP of Solution Delivery he blends his skills as a solution architect and pragmatic hands-on technologist to deliver working software solutions. He enjoys collaborating with clients and team members to develop enterprise software systems that provide right-fit solutions for the business.

 

As an architect he has expertise working with key stakeholders to drive common understanding leading to structured solutions that meet business, technical, and operational objectives. He follows a practical approach to modeling systems, applications, components, data and the flows between them to frame solutions that are feasible to implement.

 

As a technologist he has a solid foundation in web and mobile, cloud native APIs, and enabling modern platforms with the help of AWS and Kafka technologies. He has led the development of many mission-critical, end-to-end applications using a diverse set of technologies and supports a pragmatic delivery approach leveraging agile methodologies

 

Expertise: iOS and Android development, Web technologies (HTML, CSS, JavaScript, Ionic/Angular, Vue.js), APIs and enterprise applications (Spring Boot, Kotlin, Node.js, SQL, NoSQL).

 

Practical Experience: Cloud infrastructure (AWS, Google) and container orchestration (Docker, Rancher, Kubernetes), messaging (RabbitMQ) and data streaming (Kafka)

 

Interests: Micro-services architecture, cloud-based platforms and services, native & hybrid mobile development

One thought on “Layout Managers for iOS Views

  1. kevin says:

    Great article. But the images cannot be showed.

    1. Torey Lomenda says:

      Thanks for pointing that out. The images are visible again.

  2. Satish says:

    Hi Torey,
    Interesting. Would be very useful to take it a step further, by not having to specify the initial dimensions. If the layout could honor preferred dimensions of an embedded widget, that would enable dynamic UIs. I cannot download from the link. Taken it off?
    Satish

    1. Torey Lomenda says:

      The link to the samples should be working again…sorry about that.

      The layout dimensions are always in the context of the bounds of the view it is laying out. This is useful for complex UIs where you can embed layouts within the view hierarchy.

      The initial parameters when setting up a layout manager are for margins and paddings not dimensions.

      Hope the link works for you now. Thanks for the feedback, feel free to continue the dialogue.

  3. jitender says:

    thanx….i am new and it help me a lot to understanding about custom layout

  4. Izzy says:

    “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.”

    I find autolayout much closer to CSS than “springs and struts” ever was.

    If I had a nickel for every time I had to write CSS to “center this vertically between that and the other thing” … 🙂

Leave a Reply

Your email address will not be published.

Related Blog Posts
Natively Compiled Java on Google App Engine
Google App Engine is a platform-as-a-service product that is marketed as a way to get your applications into the cloud without necessarily knowing all of the infrastructure bits and pieces to do so. Google App […]
Building Better Data Visualization Experiences: Part 2 of 2
If you don't have a Ph.D. in data science, the raw data might be difficult to comprehend. This is where data visualization comes in.
Unleashing Feature Flags onto Kafka Consumers
Feature flags are a tool to strategically enable or disable functionality at runtime. They are often used to drive different user experiences but can also be useful in real-time data systems. In this post, we’ll […]
A security model for developers
Software security is more important than ever, but developing secure applications is more confusing than ever. TLS, mTLS, RBAC, SAML, OAUTH, OWASP, GDPR, SASL, RSA, JWT, cookie, attack vector, DDoS, firewall, VPN, security groups, exploit, […]