iOS - When to layout your views

Ten months ago I wrote an article about the lifecycle of the UIViewController.

My context for writing that post was various issues that I had encountered whilst developing a product based around tabbed content.

More recently I have been working on a project where I have encountered issues associated with laying out views within embedded scroll views.

This post seeks to fill in the gaps on when and where to add/manipulate your views within any context such that your app can be as visually appealling as possible.

What am I trying to achieve

What I want to do is to have:

  • Three pages of content which a user can swipe (left/right) between.

  • The content of each page can be of variable length, and as such each 'page' needs to be vertically scrollable.

  • Because some of the content on each page is dynamically loaded, I want to be able to overlay a loading screen onto each 'page' whilst the various background tasks are executing.

  • Visually, upon opening the view controller (in a modal), I want the inital page to be displayed with the loading overlay in place.

When to layout your views

I personally (wherever possible) would always recommend laying out your views within a storyboard and adding your autolayout constraints utilizing the intuitive GUI available in XCode. To all extents and purposes interface builder is a What You See Is What You Get (WYSIWYG) editor, so assuming you are not doing anything too complex it is ideal for laying out your product.

Unfortunately autolayout and scroll views work slightly differently within interface builder. If you have not already, I highly recommend reading Apples tech notes on UIScrollView And Autolayout.

The long and the short of it is that you define constraints for the contentView of your scroll view rather than the frame of the scroll view itself. Given this, it is quite apparent as to why working with embedded scroll views might get complicated.

Given this, I chose to place a single UIScrollView within my storyboard. I added no content, and simply bound each side to its respective edge. This scroll view is to act as a container for my page content - it is this scroll view that will allow me to scroll each page left/right.

I chose to then build my page content externally in a freeform Xib. The trick for implementing my paging concept was to place three UIView elements within my Xib each with equal width constraints. One can then simply organize their page content within said views.

Laying out your paged content within an Xib

Programatically adding the content

Whilst the seperation outlined above allows for you to build your views in a clean and efficient manner, at some point you need to actually load the content from the Xib and place it into your container scroll view.

To load a view from a Xib I utilize the following code (which I place within an extension of UIView).

    //Load a nib with a given name
    class func loadFromNibNamed(nibNamed: String, bundle : NSBundle = NSBundle.mainBundle()) -> UIView! {
        
        return UINib(nibName: nibNamed, bundle: bundle).instantiateWithOwner(nil, options: nil)[0] as? UIView
    }

The lifecycle of a UIViewController is extremely important at this point. A basic overview of the timings of the lifecycle methods (that pertain to views) is as follows:

  • viewDidLoad - the class has loaded but individual elements (subviews) are not available to be manipulated, and constraints have not been fully resolved.

  • viewWillAppear - the view will appear but as of yet it has not been added to the view heirachy.

  • viewDidAppear - the view has been added to the view heirachy. All its constraints have been resolved (and you can see the result on your screen).

None of these places seem particularly appropriate to programatically add my content.

In the first two methods, the view is not in the heirachy - you cannot add a subview to a view that isnt there.

By the time we have gotten to viewDidAppear however.. the view has appeared. As such, adding a subview at this point will neccesarily result in a flicker. No thanks.

What you really want to do is add the content view when the view is laying out all of its subviews i.e at the same time as the container scroll view has its constraints resolved.

UIView and layoutSubviews

The UIView class has a method layoutSubviews. This method is called to do exactly what it says on the tin - layout the subviews of that particular view.

This StackOverflow question outlines when exactly layoutSubviews is called.

When the iOS system is building your view, each subview is 'added'. The system follows the heirachy of subviews downwards adding subviews and calling their layoutSubviews method.

Given this, if you wanted to add a border to a UIView, a simple way to do so would be to create a custom class (that extends UIView) and add the border programatically within an overwritten layoutSubviews definition.

One consideration with this is that any manipulation you do will be executed every time layoutSubviews is called. Because of this one should be careful to only conditionally manipulate the view.

For example, when building a pull to refresh component I made sure to only add the control the first time that layoutSubviews was called (using a simple boolean check).

Some further insight on the need to be efficient can be found by reading this.

One issue with this is that it does require you to create a custom subclass for each view you intend to programatically manipulate. I personally am happy to do this - it fits nicely within the patterns I use within my application designs, and keeps things clear and concise.

Setting a view to use a custom class within interface builder

InitializeProtocol

To make things even clearer I have built and implemented a simple protocol - InitializeProtocol.

The premise is simple - All my custom views extend a base class BaseView (which extends UIView).

Within the layoutSubviews definition of my BaseView I conditionally call the initialize method of my InitializeProtocol if (and only if) the class in question (self) implements InitializeProtocol.

Because the BaseView does not, it is up to you to decide if your subclass should implement the protocol or not.

This is essentially the delegate pattern - you delegate the responsibility of initialization to yourself if and only if the protocol has been implemented by the subclass. This additional abstraction simplifies my initialization further.

  • Create a custom view
  • Extend BaseView
  • Implement InitializeProtocol

I get the result that I want as well as a clean, easy to read, maintainable code base.

Implementing the InitializeProtocol in a custom UIView subclass

Partially solving my problem

Given the above, I can now partially solve my problem.

I can utilize the InitializeProtocol to add my content view from a Xib to the scrollView of my UIViewController (which I set up in interface builder).

To do this, I create a custom subview and implement the InitializeProtocol methods. Within initialize I load my Xib define the appropriate constraints, and add it to my scrollView. Simple.

One thing to note is that the constraints aspect of loading a view from a Xib file can get somewhat complex. In this case for example you need to add a constraint to make the width of the loaded content three times the size of your visible screen area. The equal width constraint you defined on the Xib will result in each 'page' in the Xib then being resolved to have the width of one screen (which is what we want).

The loading screen

At this point all I have left to implement is my 'loading screen' requirements.

I like to keep my codebase organized appropriately. As such the code to 'add'/'display' my loading screen(s) should be within the view class to which it relates. This is the view I have built as a Xib.

As such, I create another custom UIView subclass to represent the Xib and set it as my Xibs custom class within interface builder.

This class could simply (once again) initialize the loading screen from another Xib within an implementation of InitializeProtocol.

In my case however things are a little more complex. I only need to show the loading screen if for example a user is logged in. My design means that discerning if this is the case and general control of what is displayed should occur within my view controller.

That said, if my controller decides that a loading screen is to be displayed I still do not want there to be any visual flickering.

Meet viewDidLayoutSubviews

UIViewControllers have two additional lifecycle methods called viewWillLayoutSubviews and viewDidLayoutSubviews.

The documentation for viewDidLayoutSubviews notes that it is:

"Called to notify the view controller that its view has just laid out its subviews."

The method is potentially called multiple times. The following quotation from the docs implies this:

"When the bounds change for a view controller'€™s view, the view adjusts the positions of its subviews and then the system calls this method."

The important thing of note is that this method is called at the minimum before the view is displayed for the first time.

Combined with this knowledge I utilize stateful enumeration to display my loading screen (as appropriate) within this call.

(I have previously discussed Stateful enumeration in Android - the concepts are simple and easily transferable - it may be of interest..?)

For example within viewDidLoad one could discern if the loading screen actually needs to be shown. Then in viewDidLayoutSubviews you could have a condition that displays the loading screen if:

  • it needs to be shown

  • the current state is CONTROLLER_STATE.INITIAL

Once shown you would then update your state such that if/when viewDidLayoutSubviews is called once again the loading screen is not reshown.

Once again, consideration should be given to what you execute within viewDidLayoutSubviews, and when. It can be called multiple times, and you do not want your code to be inefficient.

Conclusion

This conclusion could also be headed TLDR :)

The answer to the initial question, "When should you layout your views in iOS?", is:

  • In interface builder if possible.
  • Otherwise.. in an implementation of layoutSubviews within a custom subclass of UIView where possible.
  • Or.. in an implementation of viewDidLayoutSubviews within your UIViewController if needed.

Consideration should always be given to the efficiency of the code you place within these methods, and there are a number of tricks for making initialization as simple as possible.

Easy right? ;)


Thomas Clowes

Thomas Clowes

I am a 28 year old software engineer from the United Kingdom. During the day I build multi platform applications. In my spare time I eat food and run marathons. Sometimes I write angry tweets.