Towards Responsive iOS Design (I): Beyond AutoLayout and Size Classes - Digital Leaves
684
post-template-default,single,single-post,postid-684,single-format-standard,ajax_fade,page_not_loaded,,select-theme-ver-3.7,wpb-js-composer js-comp-ver-5.0.1,vc_responsive

Towards Responsive iOS Design (I): Beyond AutoLayout and Size Classes

Responsive web design was maybe the most groundbreaking innovation of web design since the introduction of the HTML markup language. With his article aptly titled “Responsive web design“, Ethan Marcotte kickstarted the responsive web movement that has become the standard way of designing for the web today. This new way of approaching design was based on abstracting the specifications and constraints of our design to adapt it to different devices, generating different experiences adjusted to their characteristics and limitations:

Rather than tailoring disconnected designs to each of an ever-increasing number of web devices, we can treat them as facets of the same experience. We can design for an optimal viewing experience, but embed standards-based technologies into our designs to make them not only more flexible, but more adaptive to the media that renders them. In short, we need to practice responsive web design.

If you remove the word “web” from this quote, it would be the perfect statement of what I think software design should be, including mobile App design. Web design relies on css properties like the min/max size of the screen, with constructions such as

@media screen and (max-width: 600px) {
   li #shop-item {
      margin-right: 10px;
      font-size: 1.2em;
   }
}

to specify a different set of style constraints to apply for different device sizes. It’s worth noting that this approach works best when the different constraint properties (margins, font sizes, paddings, positions, etc…) can be described in a relational way (as percentages or proportions) rather than with absolute values.

In this post, I would like to define what I call “Responsive iOS Design”, and give some insights of how it could be applied to basic sizing, collection views and table views in iOS, thanks to technologies such as AutoLayout and Size Classes (introduced in iOS 8). As always, you can download the full code as a XCode project in my Github.

Springs and Struts

springs-strutsIn the beginning, it was very easy to design for the iPhone. There was only one model, with a fixed 3.5 inch display, and just two rotation orientations to care about (portrait and landscape). However, as time passed, Apple introduced the iPad, with a 9.7 inch display, forcing you to have two storyboards (if you used the Interface Builder) or adapt your NIBs/XIBs and UIViews to two different sizes. During this period, the developers would rely on a mechanism called “Springs and Struts” to structure their user interfaces, and define the relationships between the visual elements, and how they would react to size changes (like those produced by a rotation of the device). The springs would just specify at which positions a view will pin or adhere, and the struts would define if the visual element (view, button, etc…) was able to expand, and on which direction.

The springs and struts worked relatively well for simple interfaces, but when you needed something more complex, like adaptable text or rich, expressive collections of items with dynamic content, the developer would need to spend a lot of time programmatically customizing the interfaces from the storyboard/XIB files, involving writing a large amount of code that was not related to the problem being solved by the App.

 

Enter Auto-Layout

The springs and struts design mechanism was enough for us until Apple introduced the iPhone 5, with a 4 inch display. Suddenly we needed to take care of not just two different storyboards with different orientations, but two different iPhone sizes that were not proportional in their dimensions (the iPhone 5 was the same width as its predecessor, but had a much larger height). Springs and Struts were simply not rich enough for defining boundaries and sizes in all these different set of canvases. Luckily, alongside this new device, Apple released iOS 6, introducing a much needed AutoLayout feature, a different way of specifying the dimensions and positions of the visual elements of our Apps. Now, instead of specifying where should the view be positioned and its capacity to shrink or grow, you would set a number of constraints of every view, that would relate them with their siblings or their parents in the view hierarchy.

From the point of view of responsiveness, the set of AutoLayout constraints is very rich, and allows you to specify the space for a visual object very precisely. To be honest, I find the AutoLayout mechanism more difficult to use and time-consuming that Springs and Struts, but with such an array of different screen sizes, it is really the way to go. When it was introduced back in 2012, we still needed to build two different storyboards, or two different sets of XIB/NIB files, one for iPads and another one for iPhones. We were still far from thinking in terms of global responsiveness.

iOS 8, the iPhones 6/6Plus, and Apple Watch

In 2014, Apple presents the new iPhone 6 and iPhone 6Plus. As we already knew from leaks and rumors, these iPhones have bigger screen sizes, much more bigger. In fact, the iPhone 6Plus, with its 5.5 inch display, could be more aptly be called a phablet than a phone. With this array of different screen sizes and proportions (3.5, 4, 4.7, 5.5, 7.9, 9.7), it was clear that Apple would need to introduce a different concept for interface design, and it did with “Size Classes”.

Size classes are an abstraction of how a device should be categorized depending on its screen dimensions and relative proportions, including two categorizations for vertical and horizontal relative size called “Regular” and “Compact”, the first being for bigger spaces and the last for smaller spaces. Thus, when thinking on the design of your App, you will start with a generic design and you will adapt it to the different combinations of Regular-Compact devices’ dimensions. One of the best things about Size Classes is that they have inheritance mechanisms, meaning that you can design for “Compact width – Any height”, and your design will propagate for both compact height and regular height. Below is a chart with the different size classes and the devices that adhere to them. Notably missing is the Apple Watch, that will probably enter the Compact width-Compact height category.

Apple has introduced the concept of Size Classes beautifully in Xcode, with a default 600×600 square canvas that is smaller than an iPad, larger than an iPhone, and different to both in proportions. I think it’s a nice move as it forces you to step back from a concrete device representation, and think in terms of a generic device.

Size Classes

In Android, this problem has existed from the very beginning, being populated by a plethora of different devices with not only different screen sizes, but very different pixel densities and screen specifications. Android thus had to face this issue early, finally developing a really smart strategy for copying (uh, sorry, I meant coping ;) with adaptative interfaces, in an approach similar to CSS, employing concepts such as paddings and gravities (alignments). In my opinion, the different pixel densities forces Android designers and developers to do a lot of extra work in preparing the assets for the App. Apple, on the other hand, has traditionally worked with two pixel densities: “standard” and “retina”, which required @1x and @2x images respectively and was, compared to android, much easier. [UPDATE: Since the release of the new iPhone6 and iPhone 6Plus, now @3x has been added to the mix, making it more complex for designers and developers. Even thought developers are not working with @1x images anymore -sorry 3Gs owners-, in my opinion, Apple would need to find a way to cope with different resolutions in a more generic way, as this issue will probably increase as screen resolutions grow].

Towards full responsiveness

With the combination of AutoLayout and Size Classes in user interface design, we are closer to the concept of Responsive Mobile App Design, but we are not yet there. Why? Because responsive design is not about packaging all the content in a canvas and dumping it to the user, it is about adapting how the content is going to be shown to the user depending on the characteristics of the displaying device. It goes far beyond “getting everything that can be seen in this big screen to be shown in this smaller screen”, it is about offering a customized experience that responds to the specific needs of an output device.

responsive web designAs you can see in the image above, responsive web design has been doing this correctly for years. The background image on the smaller device (the iPhone) practically disappears, being occupied by the content. The layout also changes, from presenting the content side-by-side image+text in the bigger screen, to presenting it in a vertical flow, thus assisting the readability and showcasing the content. Fonts sizes, kernings and line heights are also different in both sizes, due to the need to boost readability on the bigger screen while keeping the typography manageable in the smaller one.

Now, with size classes, we can get closer to this kind of responsive design. In IB, we can specify the size classes we are designing for, and we are able to modify the set of constraints just for that specific size class or the ones inheriting from it. There is a lot we can customize, including font sizes, constraint values, layouts… we can even remove a set of constraints for a size class altogether and include a new set of constraints just for this size class, so we can greatly adapt our interface to the device that’s going to show it. But we are not in fully responsive realms yet…

Simple responsiveness: breaking the rules

The goal of design is to facilitate the function, making it a seamless experience, without sacrificing its beauty (form and function). In software, as in web design, the function is closely related to the content, and this should be the most important constraint of your UI design. Thus, break the rules if you need it for expressing the content in the best and most adaptive way possible. All the other constraints are secondary.

Let’s start with a basic example. You want to design a simple page with a title and subtitle on top, a button at the bottom, and a big image taking most of the screen, but adhering to some wide margins. These are your constraints. So you start to add your visual elements, add your constraints, and end up with something like this:

simpleResponsiveness1

Now, there’s nothing wrong with this design from the point of view of your requisites. You defined the width as a relative proportion (say, 75%) of the main screen size, and it “adapts” gracefully to every size and orientation. However, notice how small the image shows in the iPhone when in landscape. The purpose of this screen is to show the image to the user, and if the image is the main element of the content, we must make sure it shows properly. In such a reduced size class (compact width + compact height), it would be nice if the image could be shown in full screen mode, so why don’t we do that? The image below shows the final result if we modify the size constraints for the image, setting it as a full background image:

simpleResponsiveness2

Meanwhile, we want to keep our original design for other size classes. I think nowadays neither designers nor developers can stick to a static design for their Apps. Instead, we should be flexible enough for taking into account the limitations of different devices and adapt our designs accordingly.

Adapting the containers of content

A special consideration must be taken for those visual elements which happen to be containers of content (UICollectionView or UITableView, for instance) we should be careful to adapt how the content is displayed inside them. Suppose we have a interface where we want to show a set of images for the user to choose one. We want the images to be legible and be shown at a proper size for every device, size and orientation. This interface will also have a title and subtitle at the top and a button at the bottom, to make things more interesting. Let’s see how two different containers could adapt to this situation in two different ways:

Discrete responsiveness: UITableView

UITableViews are all over iOS. They are used by most applications found in the App Store in one way or another. Now, with Size Classes and just one main storyboard to represent the user interface’s design, we must make sure that the elements on the table will be properly sized. In a completely responsive environment, the UITableView would “expand” into a UICollectionView for regular size classes and “collapse” back to a UITableView for the compact ones, so we would show, for instante, two or three columns in a row in a big screen and just one column per row in smaller devices, just like in the responsive web design figure shown previously, but with the current state of the iOS/OSX SDK, this can’t be done without a lot of hacking and a really inefficient view containing both a UICollectionView and a UITableView, that would hide/show depending on the layout.

Thus, we will use a UITableView and will change the height of the rows depending on the current size class. As we are working with a table view, it makes sense to have only a limited set of heights for our rows, one for regular width, one for compact width but regular height, and another one for an ultracompact environment (compact width and compact height). We would also define an array of entries with the different images’ names (the images and icons used are from GraphicsFuel):

let kResponsiveTableViewCellHeightUltraCompact = 42.0
let kResponsiveTableViewCellHeightCompact = 80.0
let kResponsiveTableViewCellHeightRegular = 120.0
let entries = ["calculator", "calendar", "camera", "chat-bubble", "cloud-download", "crest", "email", "file", "heart", "location-pointer", "lock", "paper-plane", "paste", "pie-chart", "search", "settings01", "settings02", "telephone", "video-play"]

We will define our UITableViewDataSource methods using this array of entries:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
   var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(kResponsiveTableViewCellName) as UITableViewCell
   // image
   if let imageView = cell.viewWithTag(kResponsiveTableViewCellImageTag) as? UIImageView {
      imageView.image = UIImage(named: entries[indexPath.row])
    }
    // label
    if let nameLabel = cell.viewWithTag(kResponsiveTableViewCellLabelTag) as? UILabel {
       nameLabel.text = entries[indexPath.row]
    }
    return cell
}
 
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return entries.count
}

Without any height adaptation code, the elements of the table would seem too small for an iPad, and too big for an iPhone, specially in landscape, not even reaching two visible elements in a 4S:

ResponsiveTableviews

To respond to changes in size, we should set the height of the rows according to the current trait collection that defines our current size class:

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
   if traitCollection.horizontalSizeClass == .Compact { // compact horizontal size class
      if traitCollection.verticalSizeClass == .Compact { // compact vertical size class
         return CGFloat(kResponsiveTableViewCellHeightUltraCompact)
      } else { // regular vertical size class
         return CGFloat(kResponsiveTableViewCellHeightCompact)
      }
   } else { // regular size class
      return CGFloat(kResponsiveTableViewCellHeightRegular)
   }
}

Now, the table items will look great in both orientations of our “tiny” iPhone 4S, while also taking advantage of the bigger iPad’s screen:

ResponsiveTableviewsCorrect

 

Flowing responsiveness: UICollectionView

Sometimes we need a more precise control upon how our containers adapt to the information contained inside them. Take for example a collection view. Let’s suppose we want to represent the same images in a UICollectionView, but we want them to be visible at all times, maybe for being selected and deselected by the user. For this scenario, we would start by setting our UICollectionView, defining the constraints and ending up with a solution that will look great in an iPad, but not-so-great on a small iPhone:

ResponsiveCollectionviews

For this scenario, it would be great if our cells could adapt to the precise size of the UICollectionView, so that all items would be shown, no matter what’s its current size. For this, we need to implement the collectionView(collectionView:, layout: , sizeForItemAtIndexPath:) method, where we will return the size of the cell as the total available area of the UICollectionView divided by the number of entries to be displayed, minus a certain margin span:

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
   let collectionViewSize = collectionView.frame.size
   let cellSpan = traitCollection.horizontalSizeClass == .Compact ? 
      kReadyCategoryCollectionViewCellSpanCompact: kReadyCategoryCollectionViewCellSpanRegular
   let sideSize: Double = sqrt(Double(collectionViewSize.width * collectionViewSize.height) / (Double(entries.count))) - cellSpan
   return CGSize(width: sideSize * kResponsiveCollectionSizeProportion, height: sideSize)
}

We would also need to be aware of changes in the trait collection of our UIViewController, thus, we would need to implement viewWillTransitionToSize(size:, withTransitionCoordinator:), and just update our table:

override func viewWillTransitionToSize(size: CGSize, 
   withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
   collectionView.reloadData()
}

Now, the collection view items adapt to changes in orientation and size properly:

ResponsiveCollectionviewsCorrect

The way forward…

In this tutorial we have started scratching the surface of responsive iOS Design, but there is a lot more we could possibly do. One loose end is the current impossibility of iOS/Xcode to “transform” containers depending on the temporal layout constraints, i.e: responding to changes in size classes by modifying the nature of a container so it displays the information differently. For this, we would need to have a super-container capable of displaying the information in many different formats and adapt to them on the fly in response to changes in orientation, size, etc.

In further tutorials, we will explore deeper strategies for reaching full responsiveness aware designs. You can download the code of the examples shown above in my github repository.

 

Ignacio Nieto is the owner of the Digital Leaves blog, he loves building successful products that improve people’s lives. He has expertise in iOS, Full Stack JS, Android, RESTful backends, OAuth and cryptography.

18 Comments
  • Totem

    September 20, 2014 at 1:34 am Reply

    Excellent post on auto layout and size classes. Not much out there on good solid design that takes advantage of the two concepts together yet. One can seriously make a mess of their storyboard file and constraint inheritance without seeing good examples like this. Gotta love the new Preview Assistant. Thanks for sharing!

  • Savvy

    October 16, 2014 at 4:10 pm Reply

    Hi Ignacio,
    Great tutorial, gives a lot of in-sight about new layout. I am on my way to autolayou my iPhone app for iOS7 to iOS8. Working with collection view using flowlayout and custom cells. Is there a way to use aytolayout and size classes for custom cells? Please help.
    Thanks in advance.
    Savvy

    • Ignacio Nieto Carvajal

      October 16, 2014 at 6:28 pm Reply

      Hi Savvy,

      Yes of course. You can do it by defining the custom cells in a separate xib/nib file or even using storyboards. You just need to make sure to check the autolayout and “use size classes” checkboxes, but you should be able to use both with your custom UITableViewCells and UICollectionViewCells. Hope that helps.

  • Alejandro Moya

    November 20, 2014 at 10:23 pm Reply

    You lost me when tried to do a “cool” comment about android vs ios, your comment is not right, in fact, there are missing things, apple added 3x for iphone6/6+ AKA copies of galaxy note and S4/5, from the very beginning, android made it clear about using different screen sizes, as it supports a wide range of devices, from the new wear, to the TVs, this means that you will need to have different graphical resources, added to fully support the ecosystem, neither one or the other had come to a full solution on this area, i can mention copies from one or the other, like the devices i specified before, if you want to have a serious post about the goodness of the platform, try to avoid falling in the fanboy comment style.

    • Ignacio Nieto Carvajal

      November 21, 2014 at 8:27 am Reply

      I’m not even going to reply to your comment. This is not an “iOS vs Android” post, so your comment is childish and doesn’t make any sense here. This is a serious post about responsiveness in smartphones and tablets (focused on iOS), have anything worth to be added to the discussion? please proceed. Otherwise, go trolling anywhere else (If you were referring to the coping/copying thing, there is one thing we non-trolls have called “sense of humor”, I can definitely recommend it to you ;).

  • Craig Maynard

    December 7, 2014 at 10:58 am Reply

    What are the definitions of these constants:
    kReadyCategoryCollectionViewCellSpanCompact
    kReadyCategoryCollectionViewCellSpanRegular

    • Ignacio Nieto Carvajal

      December 7, 2014 at 12:26 pm Reply

      Hello to you too. They are some span values to better fit the cells. Currently, they are defined as:

      let kReadyCategoryCollectionViewCellSpanRegular = 40.0
      let kReadyCategoryCollectionViewCellSpanCompact = 20.0

      It’s all in the github code. Please have a look.

  • Kenny Deriemaeker

    January 1, 2015 at 11:22 pm Reply

    Really good article, thanks!

    I did notice an error in your size class matrix – it seems you’ve swapped the labels for iPhone Landscape and ??? (the Apple Watch) around.

    • Ignacio Nieto Carvajal

      January 3, 2015 at 12:02 pm Reply

      Hi Kenny,

      Thanks for the warning, but I can’t locate the error. This matrix is right from Apple’s developer website. The iPhone Landscape is considered Compact-Compact, and “???” does not stand for Apple Watch (in fact, the Apple Watch is considered Compact-Compact). Can you please elaborate?

  • André Slotta

    March 13, 2015 at 11:46 am Reply

    very cool and informative tutorial, thank you for this! just one quick note:
    in your very last code example (viewWillTransitionToSize) I think you do not have to reload the collectionview’s data. instead it should be enough to invalidate its layout by calling:
    collectionView.collectionViewLayout.invalidateLayout()

    • Ignacio Nieto Carvajal

      March 14, 2015 at 10:41 am Reply

      Thanks to you André for your correction. You are absolutely right, a mere call to invalidateLayout() will do the trick without requiring you to reload all the data.

  • JerryR

    July 18, 2015 at 12:40 am Reply

    I just came across this while searching for a way to resize a 4×3 fixed grid in a container view based on size classes, but one that would also adapt to future multi-tasking on the iPad when it might be possible to animate resizing of a slide-over or splitView and therefore require a multitude of different container sizes while “sliding”. You seem to be one of very few who have figured it out and posted and excellent example.

    I know I’m late to the party but wanted to thank you for taking the time to write this up.

    JerryR

  • Zahur

    October 21, 2015 at 12:27 pm Reply

    Hi there,
    First of all, thank you for the wonderful post. I downloaded the code and it was great help. Personally I also a developer, and developing small application. Where I add UICollectionView programmatically – nothing from the storyboard and added the Autolayout constraints as an when required. Currently I am facing an issue with orientation. Whenever I change the orientation the whole application freezes. And on the top of I am not getting any issues or warning in debug log.

    Your quick tip can help me out to overcome this issue.

    • Ignacio Nieto Carvajal

      October 24, 2015 at 8:27 am Reply

      Hi Zahur,

      Thanks for your comment. Glad to be helpful. I’m afraid without knowing more about your problem or some debug info I’m not able to help you. Maybe your autolayout constraints are making it crash when the device rotates, but that’s strange. Try putting some breakpoints on viewWillTransitionTo… And see what happens.

      Good luck!

  • nasser

    February 8, 2016 at 12:11 pm Reply

    Hi Ignacio,

    very good article.

    thanks a lot.

  • mja

    November 18, 2016 at 8:07 am Reply

    Thanks very much for the article. I was looking exactly for this …. thank you for taking time to share your know-how.

Post a Comment

Join the Digital Tips List
Want to become a freelance iOS developer or improve your skills? Subscribe to the Digital Tips list for weekly tips, useful tutorials and more to help you grow as a developer.
I hate spam, and I promise I'll keep your email address safe.