Scrolling Stack Views

Using a Stack View can save a lot of boilerplate auto layout code but there are times when you might wish that it acted more like a table view and scrolled its contents. The UIStackView class is not a subclass of UIScrollView but there is nothing to stop you embedding a stack view in a scroll view.

When to Use

There are a number of situations where you might consider having a stack view embedded in a scroll view. Two common ones that come to mind:

On the other hand don’t use a stack view when a table view makes more sense.

Steps to Create

Consider the example where we have a vertical stack view containing a number of images. As images get added to the stack view I want them to scroll within the content area between the labels at the top of the screen and the tab bar at the bottom (keeping the labels visible):

Vertical Stack View

This view uses a number of stack views. Here is how the scene looks in the storyboard:

Storyboard scene

The image stack view must have contraints to the leading/trailing and top/bottom edges of the stack view. An equal width constraint with the scroll view ensures the stack view fills the width of the scroll view.

For reference here are the views and constraints in Interface Builder:

IB Constraints

If you are doing this in code:

stackView.leadingAnchor.constraintEqualToAnchor(scrollView.leadingAnchor).active = true
stackView.trailingAnchor.constraintEqualToAnchor(scrollView.trailingAnchor).active = true
stackView.bottomAnchor.constraintEqualToAnchor(scrollView.bottomAnchor).active = true
stackView.topAnchor.constraintEqualToAnchor(scrollView.topAnchor).active = true
stackView.widthAnchor.constraintEqualToAnchor(scrollView.widthAnchor).active = true

Tap Gestures

To play with what happens when we add and remove views to the stack view I have added three tap gestures each of which has an action method in the view controller:

Adding Views to the Stack View

The function that responds to a single tap adds a heart image to the stack view and then scrolls to make the added image visible:

@IBAction func singleTap(sender: UITapGestureRecognizer) {
  let heartImage = UIImage(named: "Heart")
  let heartImageView = UIImageView(image: heartImage)
  stackView.addArrangedSubview(heartImageView)
  scrollToEnd(heartImageView)
}

The two finger tap method is similar so I will skip it. Here is the three finger tap action to empty the stack view:

@IBAction func threeFingerTap(sender: UITapGestureRecognizer) {
  let views = stackView.arrangedSubviews
  for entry in views {
    stackView.removeArrangedSubview(entry)
    entry.removeFromSuperview()
  }
}

Scrolling

Scrolling to the end of the stack view can be a little tricky. At the point when we add the view to the stack view the system has not yet done a layout pass. This means it has not yet recalculated the bounds of our stack view and hence the content size of the scroll view.

Since we know the size of the view we just added we can figure out the new content offset for the scroll view. Here is what I came up with in the end:

private func scrollToEnd(addedView: UIView) {
  let contentViewHeight = scrollView.contentSize.height + addedView.bounds.height + stackView.spacing
  let offsetY = contentViewHeight - scrollView.bounds.height
  if (offsetY > 0) {
   scrollView.setContentOffset(CGPoint(x: scrollView.contentOffset.x, y: offsetY), animated: true)
  }
}

If we have everything setup we should be able to scroll when the stack view grows beyond the visible bounds of the scroll view:

Scrolling Stack View

Sample Code

You can find the full working examples from this post in the Stacks Xcode project in my GitHub Code Examples repository.

Further Reading

Never miss a post!

iOS Size Classes Cheat Sheet

Subscribe and get my free iOS Size Classes Cheat Sheet

Unsubscribe at any time.
No time to watch WWDC videos?

Sign up to get my iOS posts direct to your inbox and I will send you a free PDF of my iOS Size Classes Cheat Sheet.

Unsubscribe at any time.
Archives Categories
comments powered by Disqus