Search code examples
swiftautolayoutscrollview

swift scrollView subview width always 0?


I met a strange problem, the subviews width is always 0 when I add it as subview of scrollView, but it works when I give the subview a specific number such as make.width.equalTo(200).

I was wondering if it is because the scrollView cannot get the right width, but I check the hierarchy of screen it shows width is 393. Why the subviews suddenly cannot get right width.

Here is my demo:

class ViewController: UIViewController {
    private lazy var scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.delegate = self
        scrollView.alwaysBounceVertical = true
        scrollView.contentInsetAdjustmentBehavior = .always
        scrollView.backgroundColor = .white
        return scrollView
    }()
    
    private lazy var viewOne: UIView = {
        let view = UIView()
        view.backgroundColor = .blue
        return view
    }()
    
    private lazy var viewTwo: UIView = {
        let view = UIView()
        view.backgroundColor = .yellow
        view.alpha = 0.5
        return view
    }()
    
    private lazy var viewThree: UIView = {
        let view = UIView()
        view.backgroundColor = .red
        view.alpha = 0.5
        return view
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        setupUI()
    }
    

    private func setupUI() {
        view.backgroundColor = .white
        
        view.addSubview(scrollView)
        scrollView.addSubview(viewOne)
        viewOne.backgroundColor = .blue
        scrollView.addSubview(viewTwo)
        scrollView.addSubview(viewThree)
        
        scrollView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        
        viewOne.snp.makeConstraints { make in
            make.top.equalToSuperview()
            make.left.right.equalToSuperview()
            make.height.equalTo(200)
        }
        
        viewTwo.snp.makeConstraints { make in
            make.top.equalTo(viewOne.snp.bottom).offset(12)
            make.left.right.equalToSuperview()
            make.height.equalTo(200)
        }
        
        viewThree.snp.makeConstraints { make in
            make.top.equalTo(viewTwo.snp.bottom).offset(12)
            make.left.right.equalToSuperview()
            make.height.equalTo(200)
            make.bottom.equalToSuperview()
        }
    }
}

extension ViewController: UIScrollViewDelegate {
    
}


Solution

  • Starting with iOS 11, a UIScrollView has 3 "frames" ...

    • the view .frame itself
    • the .contentLayoutGuide
    • the .frameLayoutGuide

    This was done because constraining subviews to the scroll view was ambiguous.

    Constraints to the .contentLayoutGuide control the actual .contentSize. So, if you constrain a subview leading and trailing to the .contentLayoutGuide, and give that subview a width: 2000, the scroll view will scroll 2000-points horizontally.

    If you want to size your subview(s) relative to the size of the scroll view's frame, make use of the .frameLayoutGuide.

    Take a look at the changes here:

    private func setupUI() {
        view.backgroundColor = .white
        
        view.addSubview(scrollView)
        scrollView.addSubview(viewOne)
        viewOne.backgroundColor = .blue
        scrollView.addSubview(viewTwo)
        scrollView.addSubview(viewThree)
        
        scrollView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        
        viewOne.snp.makeConstraints { make in
            make.top.equalTo(scrollView.contentLayoutGuide)
            make.left.right.equalTo(scrollView.contentLayoutGuide)
            make.height.equalTo(200)
            // width equal to scrollView's frameLayoutGuide width
            make.width.equalTo(scrollView.frameLayoutGuide)
        }
        
        viewTwo.snp.makeConstraints { make in
            make.top.equalTo(viewOne.snp.bottom).offset(12)
            make.left.right.equalTo(scrollView.contentLayoutGuide)
            make.height.equalTo(200)
            // width equal to scrollView's frameLayoutGuide width
            make.width.equalTo(scrollView.frameLayoutGuide)
        }
        
        viewThree.snp.makeConstraints { make in
            make.top.equalTo(viewTwo.snp.bottom).offset(12)
            make.left.right.equalTo(scrollView.contentLayoutGuide)
            make.height.equalTo(200)
            make.bottom.equalTo(scrollView.contentLayoutGuide)
            // width equal to scrollView's frameLayoutGuide width
            make.width.equalTo(scrollView.frameLayoutGuide)
        }
    }
    

    enter image description here