UIScrollViews and auto layout

:  ~ 1 min read

UIScrollViews are a different kind of beast when created with auto layout, but the basic idea is to constrain it to a fixed frame, and let the constraints of its subviews determine its contentSize. So let's start with that:

private lazy var scrollView: UIScrollView = {
  let sv = UIScrollView(frame: CGRect.zero)
  sv.translatesAutoresizingMaskIntoConstraints = false
  sv.alignTop(nil, leading: nil, bottom: nil, trailing: nil, toView: self.view)
  return sv

We have a scrollView that is the same size of our view, nothing fancy.

Let's say we have two layouts, one with 3 subviews, one with 2, at 200px height each, spaced out by 10px. Still nothing fancy, but the catch is that the last subview has to always be pinned to the bottom of the screen.

On devices with the screen height >= 630px, this will be simple, because scrolling won't be needed - pin the bottom edge of the last subview to the view itself. What about the other devices?

On the first layout, the contentSize.height of our scrollView will be 3 * 200 + 3 * 10 = 630px - when scrolled to the bottom, the last subview will be at the very bottom. All good, so far.

On the second layout, the contentSize.height of our scrollView will be 2 * 200px + 2 * 10 = 420.

We have to constrain the last subview's bottom edge with the scrollView's bottom edge, to determine its contentSize.height, and, sure, we could calculate the required top space on each device / layout and modify the constant accordingly, but the solution is much more simple and flexible:

// Add it to the scrollView, just like the rest
// Even though the scrollView is constrained to the view's height, 
// don't be mistaken: the lastSubview won't be pinned to the bottom; 
// this constraint here only determines the scrollView's contentSize.height
lastSubview.alignBottomEdgeWithView(scrollView, predicate: nil)

var lastSubviewTopConstraint: NSLayoutConstraint?
var lastSubviewBottomConstraint: NSLayoutConstraint?

if screenHeight < 630 {
  lastSubviewTopConstraint = lastSubview.constrainTopSpaceToView(secondSubview, predicate: "10@999")
  // @999 means priority 999 (we're starting in layout 1)

// Also constrain the bottom to the view itself
lastSubviewBottomConstraint = lastSubview.alignBottomEdgeWithView(view, predicate: "0@1")
// @1 means priority 1

// Then just toggle their priorities:
lastSubviewTopConstraint?.priority = layoutOne ? 999 : 1
lastSubviewBottomConstraint?.priority = layoutOne ? 1 : 999

// The bottom constraint on the scrollView always determines the scrollView.contentSize.height. 
// In addition, the top constraint determines the lastSubview's position, when the high priority is set.
// The bottom constraint on the view determines the lastSubview's position, when the high priority is set.