Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gap at the top of UIScrollView on iOS10

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?

comparison of screenshots from iPhone 5s simulator

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
like image 642
death_au Avatar asked Sep 19 '16 02:09

death_au


1 Answers

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.

like image 113
Zico Avatar answered Jan 31 '23 12:01

Zico