Search code examples
iosswiftautolayoutuitraitcollection

Is UITraitsCollection the path to having diff iPad Landscape vs Portrait AutoLayout in IB/Storyboard


The Ask: I want to have different layouts between Portrait vs Landscape on the iPad. (I'm successful on the iPhone But iPad share similar size classes)

What I've done/Tried:

Reading and searching thru Google and SO, I haven't found the solution. What I did learn was about overriding UITraitsCollection and If possible, I would like to force the iPad Landscape configuration to follow the same one which I've already set up for the iPhone Landscape mode using hCompact

I tried this solution https://stackoverflow.com/a/60409218/14414215 but unsure where to put it and when just placed within the UIViewController, when running, the app doesn't even call it. (It's likely cos I don't have my views within a childViewController?)

This post here also references UITraitsCollection https://stackoverflow.com/a/41374705/14414215 however there is a lack of an example.

I'm fairly sure that this is something that many apps are doing but I'm not able to find the solution.

For reference: This is what I want to achieve on the iPad as well. (iPhone works great using this solution from @DonMag)

StackView layout side by side & on Top of each other (like zStack)


Solution

  • With different device screen aspect ratios, and the multi-tasking capabilities on iPads, you're encouraged to think in terms of size not orientation.

    If your app is being run with Slide Over / Split View, the size class can be wC hR even when the device is in "landscape orientation."

    If you want to change layout based on aspect ratio:

    • if width > height use a "landscape" layout
    • if height > width use a "portrait" layout

    You may want to create your own constraint collections and activate / deactivate them in viewWillTransition(to size:...)

    If you want to stick with Trait variations, you'll run into problems trying to manipulate them for the "root" view of a controller.

    You could try making your controller a child view controller, either by embedding it in a UIContainerView or adding it via code, and then implementing (in the "parent" view controller):

    class ParentViewController: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            if let vc = storyboard?.instantiateViewController(withIdentifier: "myVC") as? MyRealViewController {
                addChild(vc)
                view.addSubview(vc.view)
                vc.didMove(toParent: self)
            }
            
        }
        
        override func overrideTraitCollection(forChild childViewController: UIViewController) -> UITraitCollection? {
            if childViewController.view.bounds.width > childViewController.view.bounds.height {
                let collections = [UITraitCollection(horizontalSizeClass: .regular),
                                   UITraitCollection(verticalSizeClass: .compact)]
                return UITraitCollection(traitsFrom: collections)
            }
            return super.overrideTraitCollection(forChild: childViewController)
        }
        
    }