Search code examples
iosswiftuiscrollviewautolayout

Why UIScrollView don't work if you set leading and trailing anchors for subViews?


Example:

class ViewController: UIViewController {

  let labelOne: UIView = {
    let label = UIView()
    label.backgroundColor = .red
    label.translatesAutoresizingMaskIntoConstraints = false
    return label
  }()

  let scrollView: UIScrollView = {
    let v = UIScrollView()
    v.translatesAutoresizingMaskIntoConstraints = false
    v.backgroundColor = .cyan
    v.contentSize = CGSize(width: 2000, height: 2000)
    return v
  }()


  override func viewDidLoad() {
    super.viewDidLoad()
    self.view.addSubview(scrollView)
    scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0).isActive = true
    scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
    scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8.0).isActive = true
    scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
    scrollView.addSubview(labelOne)
    
    labelOne.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16.0).isActive = true
    labelOne.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 16.0).isActive = true
    labelOne.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -16).isActive = true
    labelOne.heightAnchor.constraint(equalToConstant: 100).isActive = true
  }
}

P.s I know it would be better if I put the content view in a UIScrollView and build a view hierarchy in that content, but I'm genuinely wondering why this is happening


Solution

  • Take a look at this...

    We set the labelOne (red view):

    • width to 1968-pts (2000 minus 16-pts on each side)
    • height to 100-pts
    • top, leading and trailing at 16-pts to top, leading and trailing of the scroll view's .contentLayoutGuide
    • bottom at 1884-pts from the bottom of the scroll view's .contentLayoutGuide

    So:

    class ViewController: UIViewController {
        
        let labelOne: UIView = {
            let label = UIView()
            label.backgroundColor = .red
            label.translatesAutoresizingMaskIntoConstraints = false
            return label
        }()
        
        let scrollView: UIScrollView = {
            let v = UIScrollView()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.backgroundColor = .cyan
            // don't set this
            //v.contentSize = CGSize(width: 2000, height: 2000)
            return v
        }()
        
        
        override func viewDidLoad() {
            super.viewDidLoad()
            self.view.addSubview(scrollView)
            scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0).isActive = true
            scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
            scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8.0).isActive = true
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
            scrollView.addSubview(labelOne)
    
            // don't constrain directly to scrollView
            //labelOne.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16.0).isActive = true
            //labelOne.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 16.0).isActive = true
            //labelOne.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -16).isActive = true
            //labelOne.heightAnchor.constraint(equalToConstant: 100).isActive = true
            
            // if we want horizontal scrolling of 2000-pts
            //  and labelOne to have 16-pts space on each side
            labelOne.widthAnchor.constraint(equalToConstant: 2000.0 - 32.0).isActive = true
            
            // labelOne height = 100
            labelOne.heightAnchor.constraint(equalToConstant: 100.0).isActive = true
            
            // use scrollView's Content Layout Guide
            let g = scrollView.contentLayoutGuide
            
            // labelOne 16-pts from top
            labelOne.topAnchor.constraint(equalTo: g.topAnchor, constant: 16.0).isActive = true
            
            // labelOne 16-pts from leading
            labelOne.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0).isActive = true
            
            // labelOne 16-pts from trailing (so we can scroll 2000-pts
            labelOne.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0).isActive = true
            
            // if we want vertical scrolling of 2000-pts
            //  and labelOne to be 100-pts tall with 16-pts on top
            //  constrain labelOne bottom (2000 - (100 + 16)) from content guide bottom
            labelOne.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -(2000.0 - (100.0 + 16.0))).isActive = true
    
        }
    }