UIKit

How to create UIScrollViews Programmatically

Learn how to create and manage UIScrollView and its content programmatically

Storyboards and Interface Builder(IB) are great when you start a new UIKit based iOS project. However they can soon loose their appeal as you develop more complex screens, UIs and user flows.

Alternatively you can start develop your UI programmatically. However developing and managing UIScrollView’s programmatically can be tricky and easy to forget how to.

In this tutorial I’ll show you how to create a UIScrollView and manage its content programmatically. We’ll be starting off from an existing iOS app project.

In the app we’ll be displaying the recipe on how to make Paella using UILabels, UIStackViews and UIImages. The views and the contents will be provided. The content views overflows the bounds of the screen. Thus we’ll embed the recipe contents in a UIScrollView programmatically.

For this tutorial I have used Xcode 12.3 and Swift 5.3.2. I assume you have intermediate experience developing iOS apps using Swift. I also assume you already know some experience building UI’s programmatically or an understanding of it.

Getting Started

Let’s start by downloading the starter pack. Open Terminal app and execute the following commands:

cd $HOME
curl https://github.com/anuragajwani/programmatic_uiscrollview_tut/archive/starter.zip -o programmatic_uiscrollview.zip -L -s
unzip -q programmatic_uiscrollview.zip
cd programmatic_uiscrollview_tut-starter
open -a Xcode PaellaRecipe.xcodeproj

In this post we’ll be only focusing in ViewController.swift file. Open ViewController.swift in Xcode.

We’ll be building the following screen:

Displaying Paella Recipe in a UIScrollView Programmatically

In this section we’ll be creating the UIScrollView and adding the views to guide the user on how to cook a Paella programmatically.

Here are the steps we’ll take to achieve this:

  1. Create UIScrollView
  2. Insert contents to content view

Let’s do this!

1. Create UIScrollView

First let’s create and insert an instance of UIScrollView that takes the whole screen in the ViewController. Open ViewController.swift and add the following properties to it:

private var scrollView: UIScrollView!
private var scrollContentView: UIView!

The scrollView property will hold the absolute position of the UIScrollView in the screen whilst the scrollContentView will hold the scrollable content.

Next lets load the UIScrollView. Add the following function to ViewController:

private func loadScrollView() {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
self.view.alignSubview(scrollView)
self.scrollView = scrollView

let contentView = UIView()
scrollView.alignSubview(contentView)
self.scrollContentView = contentView

NSLayoutConstraint.activate([
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
contentView.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.heightAnchor),
])
}

Above we are doing simple operations. We are creating an instance of UIScrollView and then aligning it to the edges of the screen (alignSubview). Then we are creating an instance of the view that will hold the contents (contentView) and again we are aligning this to the scrollView.

Note we are also setting the width and height constraints additionally. This is the part that always gets me. First for this tutorial we have set the widthAnchor to the be equal to the scrollView. This is because we don’t want horizontal scrolling. We do however want vertical scrolling when the contents overflow the bounds of the UIScrollView. Thus we set the heightAnchor greater than or equal to the height of UIScrollView.

Also note alignSubview is a convenience function provided in a UIView extension (UIView+alignSubview.swift).

Next let’s call the loadScrollView function in loadViews insert the following line in loadViews:

self.loadScrollView()

2. Insert contents to content view

Next let’s insert the content. The content components has been already provided in the starter project. In this step we will just insert them onto our screen.

We’ll be placing the contents in a UIStackView. Add the following function to ViewController:

private func loadContents() {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 16
self.scrollContentView.alignSubview(stackView, withMargin: 15)
}

Next let’s call the loadContents function at the of loadViews:

self.loadContents()

Next let’s add the functions to create each individual view element to add to the stack view:

private func loadImageView() -> UIImageView {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = self.paellaImage
imageView.contentMode = .scaleAspectFit
return imageView
}

private func loadHeaderView() -> RecipeHeaderView {
let headerView = RecipeHeaderView()
headerView.translatesAutoresizingMaskIntoConstraints = false
return headerView
}

private func loadStepsView() -> RecipeStepsView {
let stepsView = RecipeStepsView()
stepsView.translatesAutoresizingMaskIntoConstraints = false
return stepsView
}

Finally let’s call each view creation function and add the view to our UIStackView. Add the following lines to the end of loadContents:

stackView.addArrangedSubview(self.loadImageView())
stackView.addArrangedSubview(self.loadHeaderView())
stackView.addArrangedSubview(self.loadStepsView())

If you run the app now you’ll find that the imageView has some padding in it. Run it on a smaller size iPhone like the iPhone 8 as contents might not overflow in larger iPhones.

The reason for this is that the imageView inital width and height are the image width and height. Then AutoLayout adapts the width. However there is no constraint on the height. Let’s fix that so the imageView height adapts proportionally to the imageView width based on the image aspect ratio.

Replace stackView.addArrangedSubview(self.loadImageView()) with the following three lines:

let imageView = self.loadImageView()
stackView.addArrangedSubview(imageView)
NSLayoutConstraint.activate([
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: self.paellaImage.size.height / self.paellaImage.size.width)
])

Run the app again and see the contents scroll!

Summary

In this post we learnt:

  • how to create UIScrollView’s and its content programmatically

Final Thoughts

There are many reasons why an iOS developer should build their UIs programmatically. However the one I’d like to highlight here is that Storyboards and Interface Builders files are hard to read and review. As soon as a project has 2 developer or more consider building UIs programmatically.

Additionally I believe that understanding UIScrollView is important to each and every developer. Why? Because some visually impaired users will use the Dynamic font size accessibility feature. When we design and develop a screen to fit to screen size we don’t take into account that screen contents will most likely overflow when the user uses larger than default font size. I had covered this on previous post of mine and which the screen in this tutorial is based on:

For more on iOS development follow me on Twitter or Medium!

Senior iOS Engineer @ Onfido. Writing weekly blogs on iOS and programming. Follow me to stay tuned!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store