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 UILabel
s, UIStackView
s and UIImage
s. 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:
- Create UIScrollView
- 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: