Recently i've been porting sections of our iPadOS client over to iOS, which means a lot of size class dependent layouts and reworking some of our horizontal layouts into vertical ones. Some of these sections have been around a long time and were never intended to work on smaller screen sizes.


My approach to this problem is to put all of our existing horizontal layouts into UIStackViews and toggle between horizontal and vertical layouts based on the size class, then where necessary add the UIStackView into a UIScrollView. As UIStackViews dictate their own width or height (depending on the axis), when used in conjunction with a UIScrollView the contentSize property is automatically set, meaning it self sizes based on the width or height of the child UIStackView! Pretty convenient not having to deal with contentSize calculations if you're working with content of varying lengths.

You can do this layout in code or using an xib or Storyboard. Below is an example of this working in code, where the UIView hierarchy looks like this:

UIView
---UIScrollView
------UIStackView
---------UIView
---------UIView
---------UIView

I added a UIScrollView to the root view of the UIViewController, and added a UIStackView with 3 dummy subviews, each one with its own height constraint.  

class ViewController: UIViewController {

    override func loadView() {
        super.loadView()
        loadScrollView()
    }
    
    func loadScrollView() {
    	// UIScrollView
        let scrollView = UIScrollView(frame: .zero)
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(scrollView)
        
        NSLayoutConstraint.activate([
            scrollView.leadingAnchor.
            	constraint(equalTo:view.leadingAnchor),
            scrollView.trailingAnchor.
	            constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.
	            constraint(equalTo: view.bottomAnchor),
            scrollView.topAnchor.
    	        constraint(equalTo: view.topAnchor),
        ])
        
        // Dummy Views
        let topView = UIView(frame: .zero)
        topView.backgroundColor = .red
        topView.translatesAutoresizingMaskIntoConstraints = false

        let middleView = UIView(frame: .zero)
        middleView.backgroundColor = .orange
        middleView.translatesAutoresizingMaskIntoConstraints = false

        let bottomView = UIView(frame: .zero)
        bottomView.backgroundColor = .green
        bottomView.translatesAutoresizingMaskIntoConstraints = false
		
        // UIStackView
        let stackView = UIStackView(arrangedSubviews: [
	        topView, 
	        middleView, 
        	bottomView
        ])
        
        stackView.axis = .vertical
        // equal spacing means our views can be different heights
        stackView.distribution = .equalSpacing
        stackView.spacing = 20
        stackView.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            topView.heightAnchor.constraint(equalToConstant: 400),
            middleView.heightAnchor.constraint(equalToConstant: 300),
            bottomView.heightAnchor.constraint(equalToConstant: 400),
        ])

        scrollView.addSubview(stackView)
		
        // Add stack view to scroll view
        NSLayoutConstraint.activate([
            stackView.topAnchor.
            	constraint(equalTo: scrollView.topAnchor),
            stackView.bottomAnchor.
            	constraint(equalTo: scrollView.bottomAnchor),
            stackView.leadingAnchor.
            	constraint(equalTo: scrollView.leadingAnchor),
            stackView.trailingAnchor.
            	constraint(equalTo: scrollView.trailingAnchor),
            stackView.centerXAnchor.
            	constraint(equalTo: scrollView.centerXAnchor)
        ])
    }
}

That's it! You can translate the constraints in that code fairly simply to an xib or Storyboard file if you'd prefer.

The result looks like this

Note: The same layout could easily be achieved using a UICollectionView if you'd prefer.

Thanks for reading! ✌️