Layout iPad Landscape and Multitasking Apps

Note:You can find a video of this tip here on LinkedIn Learning

If there’s one thing in auto layout that drives me nuts it is laying out for landscape different than portrait on an iPad. Phones are easy since the class sizes are different for portrait and landscape. For all phones, if I have a compact height, I know I’m in landscape. But iPads are regular width, regular height, and so I have no clue to programmatically change the layout. Let’s look at one way to handle this.
Download the example file. Take a look at the storyboard.

I have a large number of buttons and an image. I want in landscape buttons to the side, while in portrait buttons below. I set this up with nested stack views, so by switching between vertical and horizontal axis I can set my layout appropriately.

Head to the ViewController code. What I’m going to do is look at the device rotation and decide what orientation I should be in. I need to generate orientation notifications. At the bottom of the code, in viewWillAppear, add a start method from UIDevice‘s singleton current:

UIDevice.current.beginGeneratingDeviceOrientationNotifications()

As s good developer I’ll shut this down when not on this page in viewWillDisappear.

UIDevice.current.endGeneratingDeviceOrientationNotifications()

Before I layout out the subview, I’ll change the orientation of the stack view and use a different image. I made methods for the different layouts. I’ll tell the system which layout I want in viewWillLayoutSubviews:
First I’ll get the orientation from UIDevice.

let orientation = UIDevice.current.orientation

The orientation is an enum. There’s two cases for landscape, and I’ll put both in an if statement, going to landscape if true and portrait if false:

if (orientation == .landscapeLeft)||(orientation == .landscapeRight) {
    landscapeLayout()
} else { //portrait
    portraitLayout()
}

This works for both iPhones and iPads. I got an iPad Pro 11″ and an iPhone XR warmed up and ready to go on the simulator. I’ll choose to run the iPad Pro in the simulator.and run the app.

Once it comes up , I’ll click simulator and drag the app into the iPhone XR then run it

Rotating them with Command-Right Arrow changes the layout.

But there’s a problem on an iPad. Add an extra pane from Safari. Multitasking lets me add another pane, and those don’t layout the way I’d like.

The key here is class sizes. With one exception, all the multitasking panes on an iPad act like portrait on the phone as compact width, regular height. The exception, the 2/3 pane in landscape, acts like an iPad with regular width, regular height. If you want this behavior, you don’t have to do anything, But in portrait I always want my portrait layout.
I’ll first check if this is a regular height view. In landscape, I’m on an iPad if that is true, so I’lll add some code there. I’ll then check for a compact width. I have a smaller multipane if that is true. If false,  we have landscape.  I’ll add this:

if isRegularHeight && isCompactWidth{
    portraitLayout()
} else {
    landscapeLayout()
}

Run this. You get the portrait layout in 2/3.

Change the pane size to 1/2 or 1/3, and it goes back to portrait.

 

Rotate  to landscape and it will stay portrait

.

Try with the phone and it works too. With a small bit of code,  you can get rotation and multipane layout for your iPad that reflects your iPhone layout.

The Whole Code

You can find the download of the completed project here. You can find a video of this tip here on LinkedIn Learning

//
//  ViewController.swift
//  Multipane rotation - Demonstrates landscape layout on iPad
//
//
//  A exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//
import UIKit

class ViewController: UIViewController {
//Outlets
    @IBOutlet weak var pizzaImageView: UIImageView!
    @IBOutlet weak var rootStackView: UIStackView!

// Computed properties for convenience
    var isRegularHeight:Bool{
        return view.traitCollection.verticalSizeClass == .regular
    }
    var isCompactWidth:Bool{
        return view.traitCollection.horizontalSizeClass == .compact
    }
    
// The layout methods
    func landscapeLayout(){
        rootStackView.axis = .horizontal
        pizzaImageView.image = UIImage(named:"Pizza Landscape")
    }
    
    func portraitLayout(){
        rootStackView.axis = .vertical
        pizzaImageView.image = UIImage(named: "Pizza Portrait")
    }

    override func viewWillLayoutSubviews() {
        let orientation = UIDevice.current.orientation
        if (orientation == .landscapeLeft)||(orientation == .landscapeRight) {
            if isRegularHeight && isCompactWidth{
                portraitLayout()
            } else {
                landscapeLayout()
            }
        } else { //portrait
            portraitLayout()
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        UIDevice.current.beginGeneratingDeviceOrientationNotifications()
        
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        UIDevice.current.endGeneratingDeviceOrientationNotifications()
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.