thedjnivek thedjnivek - 1 month ago 39
Swift Question

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 :


  • ScrollView
    .
    leading
    =
    StackView
    .
    leading

  • ScrollView
    .
    trailing
    =
    StackView
    .
    trailing

  • ScrollView
    .
    top
    =
    StackView
    .
    top

  • ScrollView
    .
    bottom
    =
    StackView
    .
    bottom

  • ScrollView
    .
    width
    =
    StackView
    .
    width

  • ScrollView
    .
    height
    =
    StackView
    .
    height



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

Rob Rob
Answer

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 http://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