Building Breather (Part 1): Building a responsive UI for any screen in Storyboard

Breather is an open-source iOS app that shows the weather, air pollution and asthma conditions around you.

Alexandros Baramilis
5 min readApr 12, 2019

To raise awareness about air pollution and practice my iOS skills at the same time, I’m building an open-source app where I apply some of the latest knowledge I gain about iOS, Swift and programming. Since there is a lot of information to digest, I’m splitting it into small manageable parts:

Building a responsive UI for any screen in Storyboard

With the pattern described below, the contents spread out evenly in portrait mode and become compact and scrollable in landscape mode.

The goal here is for the UI to function well and appear reasonably good in all iOS devices. I’m not looking for perfection in this example, just for something that works well and is relatively quick to build.

The pattern is to have some spacer views with equal widths that will space out the content proportionally over the screen and some inner content views that will make it easier to align the content.

The general hierarchy of the UI is:

Scroll View → Content View → Labels, Images and Inner Content Views separated by Spacer Views → Stuff in Inner Content Views

I usually give the inner content and spacer views a darker background when I work to visualise them, like in the picture below.

Spacer views in dark grey, inner content views in light grey

The steps to make this work with Auto Layout are:

  • Add a Scroll View, pin it all around to its superview with 0 margins.
  • Add a UIView inside (name it Content View), pin it all around to its parent Scroll View, give it equal width to the View Controller’s root View and give it a height constraint of 504 (or whatever you think your content’s minimum height should be — I chose this as the minimum height that my content fits in iPhone 5S/SE without scrolling)
  • Now add all your content starting from the top and after each view add a UIView (name it Spacer View if you want)
  • For all the spacer views, pin them all around to their neighbours with 0 margins and give them all equal heights to each other (you can choose one spacer view and make all the others have equal height to it, or make each spacer view have equal height to its previous one). This will make sure the UI is evenly spaced out.
  • For the city label, pin it left and right with a constant greater than or equal to 8 and centre it horizontally
  • For the image view, give it a fixed height and width and centre it horizontally
  • For the first Inner Content View, give it one set of left and right constraints with constant 8 and priority 1000 and another set of left and right constraints with constant greater than or equal to 8 and priority 999. Then centre the view horizontally and give it a width less than or equal to 500 (or whatever number you think looks good — this will be the maximum width that an inner content view will have). For the rest of the inner content views, just give them equal width to the first one and align them horizontally. This will make the inner content views space out horizontally to meet the 8 margin, but when you go in landscape they revert to the greater than or equal 8 margin, in order not to break the maximum width constraint.
  • For the contents of the inner content views, align them just like you would inside any view. Nothing special here.

That seemed like a lot of work but once you get the hang of it it’s not very complicated.

Xcode should be happy now and not giving you any warnings, but there is one last thing we need to do in code to make this work.

  • Make an IBOutlet for the height constraint that you set for the Content View.
  • Go to the view controller and override the viewWillLayoutSubviews() method, adding the code below:
Updating the Content View’s height constraint

This puts the final nail to the UI. Now, if the height of the view is greater than 504 (or whatever you set before as your content’s minimum size), it sets the Content View’s height to the height of the view, which forces it to spread out the contents equally using the spacer views. If the height of the view is smaller, it sets it to the minimum, which forces the content to scroll.

Now you can run it on any device with no worries. In landscape mode, if the content doesn’t fit the screen, it becomes scrollable.

Running Breather on different screen sizes. (I maxed out my Simulator devices!)

(EDIT: If you’re interested in playing around with Auto Layout, you can also implement the same UI using Stack Views. The idea is that you place the labels, images and inner content views inside the Stack View, which then separates them evenly, removing the need for spacer views.)

Keep reading → Part 1 (Bonus): Populating the UI with sample data, namespacing with enums and formatting the data with Swift Extensions.

--

--