There is no need to calculate any explicit sizes when using auto layout and scroll view together. Create the scroll view in loadView
(purely-programmatic apps will most likely do it here) or viewDidLoad
:
override func loadView() {
...
addScrollView()
addScrollViewHeader()
// add subviews
addScrollViewFooter()
}
func addScrollView() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1).isActive = true
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1).isActive = true
}
There are a few rules in Apple's documentation to make it work with autolayout such as, if I remember correctly, not depending on the scroll view itself to determine the size of any subviews within it. But the idea is to make sure that the outer-most views (top-most, bottom-most, left-most, right-most) are anchored to the scroll view and the scroll view's content size will automatically scale.
So what I do is create transparent views at the top and bottom that stretch the scroll view's content size for me and then whatever content goes in the middle will expand and contract the scroll view automatically. The reason I use these header/footer views instead of simply adding a constant to the top-most constraint and the bottom-most constraint (to create some extra padding at the top and bottom of the scroll view) is because constants appear to be ignored. There must ostensibly be "physical" contact between the view and the edges of the content size to stretch it.
func addScrollViewHeader() {
scrollViewHeader.isUserInteractionEnabled = false
scrollViewHeader.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(scrollViewHeader)
scrollViewHeader.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
scrollViewHeader.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
scrollViewHeader.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollViewHeader.heightAnchor.constraint(equalToConstant: Shared.statusBarHeight + 32).isActive = true
}
// subviews in the middle
func addScrollViewFooter() {
scrollViewFooter.isUserInteractionEnabled = false
scrollViewFooter.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(scrollViewFooter)
scrollViewFooter.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
scrollViewFooter.topAnchor.constraint(equalTo: lastView.bottomAnchor).isActive = true
scrollViewFooter.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollViewFooter.heightAnchor.constraint(equalToConstant: 32).isActive = true
// and the most important constraint to make it work
scrollViewFooter.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
}