I'm having an issue with constraints on a UIScrollView which appears to be iOS10-specific. I seem to have a gap between the top of the scroll view and the content view on the inside, which is supposed to be stuck to the top.
There doesn't appear to be any gap on iOS 9, but on iOS 10 the gap appears.
To be clear, in both cases the scroll view top is pinned to the bottom of the top layout guide, which lines up with the bottom of the navigation bar perfectly. iOS 10 introduces a gap the size of the navigation bar between the top of the scroll view and the top of the content view.
I could align the top of the scroll view to the top of the top layout guide, which would put the gap underneath the navigation bar and the content view would line up fine, but on iOS 9 the content view would be underneath the navigation bar which is undesirable.
I've quickly created some playground code which demonstrates the issue below. Is there something obvious I'm missing? What changed in iOS 10 to make this an issue, and how do I work around it?
import UIKit
import PlaygroundSupport
class TestViewController: UIViewController {
var mainScrollView: UIScrollView
var contentView: UIView
init() {
self.mainScrollView = UIScrollView()
self.contentView = UIView()
super.init(nibName: nil, bundle: nil)
self.view.backgroundColor = UIColor.white
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
self.mainScrollView.backgroundColor = UIColor.green
self.contentView.backgroundColor = UIColor.blue
self.mainScrollView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.mainScrollView)
self.mainScrollView.addSubview(self.contentView)
// constrain the scroll view bounds to the view
self.view.addConstraint(NSLayoutConstraint(item: self.mainScrollView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.topLayoutGuide, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.mainScrollView, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.bottomLayoutGuide, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.mainScrollView, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.leading, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.mainScrollView, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.trailing, multiplier: 1, constant: 0))
// constrain the content view bounds to the scroll view
self.mainScrollView.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.mainScrollView, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 0))
self.mainScrollView.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.mainScrollView, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0))
self.mainScrollView.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal, toItem: self.mainScrollView, attribute: NSLayoutAttribute.leading, multiplier: 1, constant: 0))
self.mainScrollView.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal, toItem: self.mainScrollView, attribute: NSLayoutAttribute.trailing, multiplier: 1, constant: 0))
// constrain the content view's size to the view's size
self.view.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.width, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.greaterThanOrEqual, toItem: self.view, attribute: NSLayoutAttribute.height, multiplier: 1, constant: 0))
}
}
let rootViewController = TestViewController()
rootViewController.title = "Test"
let navigationController = UINavigationController(rootViewController: rootViewController)
PlaygroundPage.current.liveView = navigationController.view
In the first constraint you are setting you set it to the topLayoutGuide
bottom
. So from Apple documentation the bottom
of the topLayoutGuide
depend of the way you stack your subviews. So in your example if you set it to
self.view.addConstraint(NSLayoutConstraint(item: self.mainScrollView, attribute: .top, relatedBy: .equal, toItem: self.topLayoutGuide, attribute: .top, multiplier: 1, constant: 0))
it works.
But I regularly use the top of my subview so that I don't have "margin". So it could look like: self.view.addConstraint(NSLayoutConstraint(item: self.mainScrollView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0))
.
Hope it helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With