Search code examples
iosarraysuikit

Cannot add any subview to scrollView


I am so confused. I cannot get any subviews to appear in a scrollView

Specifically, I want to add the view of another view controller, but I can't even add any labels or anything. I cannot see any subviews in the view debugger hierarchy either

What am I doing wrong??

import UIKit

class MainViewController: UIViewController {

    var scrollView: UIScrollView {
        let scrollView = UIScrollView(frame: CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
        scrollView.isPagingEnabled = true
        scrollView.backgroundColor = .orange
        scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width * 3.0, height:  UIScreen.main.bounds.height)
        return scrollView
    }

    var leftViewController: UIViewController {
        let vC = UIViewController()
        vC.view.backgroundColor = .purple
        return vC
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(scrollView)
        leftViewController.view.frame = .init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
        addChild(leftViewController)
        scrollView.addSubview(leftViewController.view)
        leftViewController.didMove(toParent: self)
    }
}

can achieve what I want to like this with auto layout, still hard coding widths though.

class MainViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(scrollView)
        scrollView.addSubview(scrollViewContainer)
        scrollViewContainer.addArrangedSubview(redView)
        scrollViewContainer.addArrangedSubview(blueView)
        scrollViewContainer.addArrangedSubview(greenView)

        scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true

        scrollViewContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
        scrollViewContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
        scrollViewContainer.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
        scrollViewContainer.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
    }

    let scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.isPagingEnabled = true
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        return scrollView
    }()

    let scrollViewContainer: UIStackView = {
        let view = UIStackView()
        view.axis = .horizontal
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    let redView: UIView = {
        let view = UIView()
        view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
        view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
        view.backgroundColor = .red
        return view
    }()

    let blueView: UIView = {
        let view = UIView()
        view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
        view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
        view.backgroundColor = .blue
        return view
    }()

    let greenView: UIView = {
        let view = UIView()
        view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
        view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
        view.backgroundColor = .green
        return view
    }()
}

Solution

  • If nothing else, if your first example that was provided in your original question, you are using computed properties. These will return a new instance every time you reference them. You should make these stored properties:

    var scrollView: UIScrollView = {
        let scrollView = UIScrollView(frame: CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
        scrollView.isPagingEnabled = true
        scrollView.backgroundColor = .orange
        scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width * 3.0, height:  UIScreen.main.bounds.height)
        return scrollView
    }()
    
    var leftViewController: UIViewController = {
        let vC = UIViewController()
        vC.view.backgroundColor = .purple
        return vC
    }()
    

    Note the = and adding the () at the end to designate that they’re initialized with closures.

    The problem with computed properties is that because each reference returns a new instance, the child view controller’s view whose frame you adjusted is the not the same one you added as a subview. And the scroll view to which you added the child is not the same scroll view that you added to your container view controller.


    In your revised question, you shared code using constraints. If you use constraints, make sure that you set translatesAutoresizingMaskIntoConstraints to false for all the relevant views. Otherwise it will automatically translate the auto resizing mask of the view into constraints that will conflict with the new constraints that you are adding manually.

    And you should also avoid constraint(equalToConstant:), using constraint(equalTo:), referencing the main view, wherever possible. For example:

    class ViewController: UIViewController {
        let scrollView: UIScrollView = {
            let view = UIScrollView()
            view.translatesAutoresizingMaskIntoConstraints = false
            view.isPagingEnabled = true
            return view
        }()
    
        let redView: UIView = {
            let view = UIView()
            view.translatesAutoresizingMaskIntoConstraints = false
            view.backgroundColor = .red
            return view
        }()
    
        let greenView: UIView = {
            let view = UIView()
            view.translatesAutoresizingMaskIntoConstraints = false
            view.backgroundColor = .green
            return view
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            view.addSubview(scrollView)
            scrollView.addSubview(redView)
            scrollView.addSubview(greenView)
    
            NSLayoutConstraint.activate([
                // set the scroll view to be the size of the main view
    
                scrollView.topAnchor.constraint(equalTo: view.topAnchor),
                scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
    
                // set the subviews to be the size of the main view, too
    
                redView.widthAnchor.constraint(equalTo: view.widthAnchor),
                redView.heightAnchor.constraint(equalTo: view.heightAnchor),
    
                greenView.widthAnchor.constraint(equalTo: view.widthAnchor),
                greenView.heightAnchor.constraint(equalTo: view.heightAnchor),
    
                // now dictate where these subviews are in the scroll view vertically ...
    
                redView.topAnchor.constraint(equalTo: scrollView.topAnchor),
                greenView.topAnchor.constraint(equalTo: scrollView.topAnchor),
    
                redView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
                greenView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
    
                // ... and horizontally
    
                redView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
                greenView.leadingAnchor.constraint(equalTo: redView.trailingAnchor),
                greenView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor)
            ])
        }
    }
    

    Frankly, since you’re talking about full-screen child view controllers, I’d jettison this code entirely, and just use UIPageViewController (which not only gets us out of the business of sizing child view controller’s top level views, but lets us do a more efficient “just in time” creation of child view controllers, too).

    But I wanted to show what the constraints would look like if you were to do this manually.