Create a Scrollable Content View in Swift and iOS

Create a Scrollable Content View in Swift and iOS

Have you ever worked on iOS and thought, I have so many views I want to show them in the scrolling content view. But wait, won't UITableView be an option for them? Yes, sure. But there are many cases when you don't need to use the reusable cells and simply want to show multiple views horizontally or vertically stacked against each other.

In today's blog post, we will see how you can stack multiple views on iOS against each other and have them scroll horizontally and vertically. This will include any UIView subclasses such as UIButton, UILabel, UITextView and so on.

Creating a horizontally scrollable content view

First, we will see how to horizontally stack multiple views so that they start horizontally scrolling when they exceed the device width.

  1. Setting up views

First, we need one subclass of each UIScrollView and UIStackView. We will add a scroll view as a subview to a view of our view controller and a stack view as a subview to a scroll view.

Since we are using auto layout, we will set translatesAutoresizingMaskIntoConstraints property on both of them to false.


private let stackView: UIStackView = {
    let stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.axis = .horizontal
    stackView.spacing = 20
    return stackView
}()

private let scrollView: UIScrollView = {
    let scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    scrollView.showsHorizontalScrollIndicator = false
    return scrollView
}()

....

..


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

private func setupViews() {
    view.backgroundColor = .white
    view.addSubview(scrollView)
    scrollView.addSubview(stackView)
}

2. Setting up Constraints

Once the views are set up and added as subviews to their respective super views, it's time to set up constraints.

We will constrain the top, leading, and trailing edges of the scroll view to the top, leading, and trailing edges of its superview. Since we want the scroll view to scroll horizontally, we will make its height a constant.


scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.heightAnchor.constraint(equalToConstant: 50).isActive = true

For a stack view, we will constrain all its sides to its superview, that is, sides of the scroll view.


stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
stackView.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true

3. Adding Subviews

Now that we set up views and added the necessary constraints, we will add subviews to the stack view to demonstrate its scrolling behavior for the arbitrary number of child views.

💡
Please make sure to use addArrangedSubview API to add subviews to the stack view.

for _ in 0..<20 {
    let square = UIView()
    square.translatesAutoresizingMaskIntoConstraints = true
    square.widthAnchor.constraint(equalToConstant: 50).isActive = true
    square.heightAnchor.constraint(equalToConstant: 50).isActive = true
    square.backgroundColor = .blue
    stackView.addArrangedSubview(square)
}

If you run the app now, you will see multiple subviews organized and scrolling horizontally.

💡
Full source code for the horizontally scrolling content view is available in this Github gist

Creating a vertically scrollable content view

In the second part, we will see how to vertically stack multiple views so that they start vertically scrolling when they exceed the device height.

  1. Setting up Views for vertically scrolling content view

This step is almost the same as the first part. We are simply setting up the views for demonstrating vertical scrolling behavior, so no change to that.

The only thing that changes is, we are now changing the stack view axis to horizontal and setting the showsVerticalScrollIndicator property on scroll view to false since we are vertically scrolling.


private let stackView: UIStackView = {
    let stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.axis = .vertical
    stackView.spacing = 20
    return stackView
}()

private let scrollView: UIScrollView = {
    let scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    scrollView.showsVerticalScrollIndicator = false
    return scrollView
}()

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

private func setupViews() {
    view.backgroundColor = .white

    view.addSubview(scrollView)
    scrollView.addSubview(stackView)
}

2. Setting up Constraints

The part where we add constraints to views changes slightly. We want to set up constraints in such a way that all the views scroll in the vertical direction.

For the scroll view, we will constrain its top and bottom anchor to that of the safe area layout guide's top and bottom anchors. We will also constrain its leading and trailing edges to the leading and trailing edges of the view controller's view.


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

We use a bit different constraint system for constraining stack view. First, we will constrain the width of the stack view to the width of the scroll view. Next, we will constrain its top and bottom anchors to that of scroll view's.


stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true

3. Adding Subviews

Now that we set up views and added the necessary constraints, we will add subviews to the stack view to demonstrate its scrolling behavior for the arbitrary number of child views.

💡
Please make sure to use addArrangedSubview API to add subviews to the stack view.

Since we want to demonstrate the vertically scrolling behavior with arbitrary vertical length, we will use UILabel instances here.


for i in 0..<10 {
    let label = UILabel(frame: .zero)
    label.translatesAutoresizingMaskIntoConstraints = false
    label.numberOfLines = 0
    label.text = "Line 1 ds d asd as das d s sdf sd fsd fsd fsd f sdf sdf sdf sd fsd fs df sd fsd fsd f\(i)\nLine 2\(i)\nLine 3\(i)"
    label.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true
    stackView.addArrangedSubview(label)
    label.widthAnchor.constraint(equalTo: stackView.widthAnchor).isActive = true
}

💡
If you run the app now, you will see multiple subviews organized and scrolling vertically without clipping the text content.
💡
Full source code for the vertically scrolling content view is available in this Github gist

Summary

So that was all about how to make scroll view and stack view work for any kind of scrollable content in the horizontal and vertical direction. Earlier, I used to use my own implementation of scrollable view. But after using it for some time, I realized the pain behind it and started exploring other approaches and this is one of them.

I hope this article was useful to you and will be usable to implement any kind of scrollable content in your iOS app.

If you have any comments or questions, please feel free to reach out to me on LinkedIn.

If you like my blog content and wish to keep me going, please consider donating on Buy Me a Coffee or Patreon. Help, in any form or amount, is highly appreciated and it's a big motivation to keep me writing more articles like this.