Kevin Machado
Kevin Machado

Reputation: 4187

Adding dynamically ViewControllers inside StackView - UIStackView inside UIScrollView

I would like to add dynamically View Controllers inside a UIStackView. The UIStackView must be scrollable so I added it inside a UIScrollView.

I have an UI error, the content size is ambiguous, I tried to debug using Debug View Hierarchy and here it's the result :

Bug Content Size is ambiguous UIScrollView UIStackView

Sizes of each controller inside the UIStackView is .zero

Debug View Hierarchy - UIScrollView UIStackView

--

Here is my hierarchy on the StoryBoard

Architecture StackView inside ScrollView

UIStackView inside UIScrollView

The constraints :

My source code :

class ScrollViewController: UIViewController {


    @IBOutlet weak var scrollView: UIScrollView!
    @IBOutlet weak var stackView: UIStackView!

    private var strings = ["echo", "hola", "allo"]

    private var containerViews = [ContainerView]()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.setupViews()
    }

    private func setupViews() {
        self.setupContainers()
    }

    fileprivate func removeContainers() {
        for container in containerViews {
            container.uninstall()
        }
        containerViews.removeAll()
    }

    fileprivate func setupContainers() {
        removeContainers()
        for string in strings {
          let viewController = // get the view Controller from StoryBoard
          add(viewController)
        }
    }

    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let viewController = segue.destination
        let index = strings.index(of: sender as! String)!
    }

    fileprivate func add(_ viewController: UIViewController) {
        let containerView = ContainerView(parentController: self)
        containerView.install(viewController)
        stackView.addArrangedSubview(containerView)
    }

}


class ContainerView<T:UIViewController>: UIView {

    unowned var parentViewController: UIViewController
    weak var currentController: T?

    init(parentController: UIViewController) {
        self.parentViewController = parentController
        super.init(frame: CGRect.zero)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func install(_ viewController: T) {
        pushViewController(viewController, animated: false)
    }

    func uninstall() {
        if let controller = currentController {
            removeViewController(controller)
            currentController = nil
        }
    }

    fileprivate func setUpViewController(_ targetViewController: T?, animated: Bool) {
        if let viewController = targetViewController {
            parentViewController.addChildViewController(viewController)
            viewController.view.frame = self.bounds
            self.addSubview(viewController.view)
            viewController.didMove(toParentViewController: parentViewController)
        }
    }

    fileprivate func removeViewController(_ viewController: T?) {
        if let _viewController = currentController {
            _viewController.willMove(toParentViewController: nil)
            _viewController.view.removeFromSuperview()
            _viewController.removeFromParentViewController()
        }
    }

    fileprivate func pushViewController(_ controller: T, animated: Bool) {
        removeViewController(currentController)
        currentController = controller
        setUpViewController(controller, animated: false)
    }

}

I cannot scroll on the UIScrollView because the content size is not set correctly. Anyone know how to resolve this bug ?

EDIT: You can see here the git with an example of the bug : GitHub StackViewOnScrollView

Upvotes: 4

Views: 3117

Answers (1)

Rob
Rob

Reputation: 438232

You're showing us the constraints between the scrollview and the stack view. Those look fine. These constraints will define the content size of the scroll view (assuming the child's constraints are fully qualified, as discussed below). For specifics of constraints with scroll views, see https://stackoverflow.com/a/16843937/1271826.

The problem is likely the constraints within the ContainerView. I suspect that, given your error message, that you have not fully qualified the constraints of the child view controller.

Often when designing scenes in IB, we don't have to fully define the vertical constraints (because the height of the view controller's root view is constrained for you, so we focus on the top constraints, but not the bottom constraints). But in this case, since you are going to use the implicit height of the child's controls, you need to fully qualify all of the constraints, effectively something equivalent to the following. Note, there are not only the top constraint, but a bottom constraint, too. (I'll show in VFL because that's a concise way to define the constraints, but clearly you can define these in IB, not necessarily programmatically.)

V:|-[label]-[stepper]-|

You also probably want something that dictates the width of the stack view relative to the scroll view's superview, too, otherwise the width will be ambiguous, too (and it otherwise will probably make it really narrow, against the left edge).

Anyway, fully constraining the child view controller's views, that yields something like:

screen snapshot

Upvotes: 2

Related Questions