• Blog
  • Categories
    • App Store
    • Benchmarks
    • iOS
    • Objective-C
    • OS X
    • PHP
    • RV
    • Swift
    • tvOS
    • Web
  • Apps
    • Portfolio
    • Studio Pro
    • Sun & Moon
  • Photography

Projects

Remote Working, iOS, Mac OS X, and more

iOS

Unique Layouts for iPhone and iPad with Size Classes

Introduced in iOS 8, UITraitCollection added the concept of size classes to the iOS autolayout system. As we found out a year later in iOS 9, the primary purpose of these was to allow for adapting the interface layout for multitasking. The default size classes look like this:

Diagram of the iOS size classes

Unfortunately, this isn’t very useful if you’re looking to tailor the UI to each device type/orientation combo. Only the iPhones differ at all when the orientation changes, and the iPad’s size classes end up all over the board as soon as you throw multitasking into the mix:

Diagram of the iOS size classes in multitasking configurations

On a recent project I needed to be able to adjust the interface in response to an orientation change, and I wanted to be able to do it with minimal code (you know, the whole point of storyboards). The default size classes wouldn’t even come close to facilitating this, especially on the iPad. This is what I needed to accomplish:

Screenshot of ISI Photos app in both orientations

It’s important to note that I do not need to support multitasking — this app is intended to be run full screen rather than alongside another. As a result, the approach I took would likely not adapt well to that environment.

Uniquely identifying device/orientation combos

To accomplish having different layouts in each orientation for each device type I needed some way to uniquely identify the combo using only size classes. Conveniently, there are exactly four unique size class pairings to match the four unique device/orientation pairings. I ended up with this division:

  • W Regular / H Regular: iPad Portrait
  • W Regular / H Compact: iPad Landscape
  • W Compact / H Regular: iPhone Portrait
  • W Compact / H Compact: iPhone Landscape

While not strictly adhering to the standard definitions of the size classes, I feel they adequately convey the context of the environment they’re describing.

Overriding the default size classes

The overall app wraps its controller hierarchy in a navigation controller. This proved to be the ideal point for overriding the default size classes. Subclassing UINavigationController, the code for this was very simple:

override func viewWillAppear(animated: Bool)
{
    super.viewWillAppear(animated)
    self.returnPortraitTraits = self.view.frame.height > self.view.frame.width
}

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
{
    self.returnPortraitTraits = size.height > size.width
}

override func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection?
{
    if UIDevice.currentDevice().userInterfaceIdiom == .Phone
    {
        //iPhone Portrait
        if self.returnPortraitTraits
        {
            let horizontalTrait = UITraitCollection(horizontalSizeClass: .Compact)
            let verticalTrait = UITraitCollection(verticalSizeClass: .Regular)
            return UITraitCollection(traitsFromCollections: [horizontalTrait, verticalTrait])
        }

        //iPhone Landscape
        let horizontalTrait = UITraitCollection(horizontalSizeClass: .Compact)
        let verticalTrait = UITraitCollection(verticalSizeClass: .Compact)
        return UITraitCollection(traitsFromCollections: [horizontalTrait, verticalTrait])
    }

    //iPad Portrait
    if self.returnPortraitTraits
    {
        let horizontalTrait = UITraitCollection(horizontalSizeClass: .Regular)
        let verticalTrait = UITraitCollection(verticalSizeClass: .Regular)
        return UITraitCollection(traitsFromCollections: [horizontalTrait, verticalTrait])
    }

    //iPad Landscape
    let horizontalTrait = UITraitCollection(horizontalSizeClass: .Regular)
    let verticalTrait = UITraitCollection(verticalSizeClass: .Compact)
    return UITraitCollection(traitsFromCollections: [horizontalTrait, verticalTrait])
}

The Future

For myself, there’s no question that UITraitCollection needs to expand. .Regular and .Compact aren’t nearly granular enough to cover a lot of potential use cases, and with the potential for additional screen sizes down the road, this may only get worse.

What if this app did need to adapt to the various multitasking configurations? It would be virtually impossible to avoid significant amounts of code to adjust the interface. At the very least, UITraitCollection needs some ability to uniquely target both the device type and orientation without having to redefine what .Regular and .Compact are. Apple has done it before, most notably with the push towards autolayout itself and deprecation of autoresizing masks.

Ultimately I’d like to see something akin to CSS media queries. Let developers define the points where layouts change rather than using the very arbitrary .Regular and .Compact. Like the web, different change points make sense for different projects, especially as screen sizes and shapes further diverge.

Previous
Next

Copyright 2025 Ryan Britton