Chapter 5
Infinite Scrollview: Build It
Time to code – get that infinite scrollview looping.
You are going to do the following:
- Subclass
UIScrollView
(optional) - Override the
layoutSubviews()
function - Grab the
currentOffset
- Skip to the end or beginning if needed
NOTE: Subclassing
UIScrollview
is optional, there already exists anInfiniteScrollview.swift
file for you to fill in. If you choose to skip, you can head down to the Add Some Content section.However, I have included the steps to create the subclass for those of you who haven’t done this kind of thing before.
Subclass UIScrollview
Create a new file in Xcode by choosing:
File > New > File…
or, by hitting:
CMD + N
Choose:
iOS > Source > Swift File
Name the file:
InfiniteScrollView
When the file has been created, change the following line:
import Foundation
to:
import UIKit
Then, start typing the word class
and BEFORE you finish typing you should get a popup that looks like this:

Choose Swift Subclass and hit return. Xcode will fill in your new class with little spaces for you to replace.
Select name and type in InfiniteScrollView
Hit Tab
to move to the super class variable and type UIScrollView
Hit Tab
again to move to the properties and methods variable.
Start typing layoutSubviews
and when autocomplete pops up, hit return.
Next, copy the following and paste it into the code
variable within the new function you just created:
super.layoutSubviews()
Finito. You’ve just created a new subclass called InfiniteScrollView
. Your new class now works exactly like a UIScrollView
. But, it doesn’t do anything special yet… So, let’s get to infinity.
PS your function should look like this:
override func layoutSubviews() {
super.layoutSubviews() //calls the parent class method
}
Putting It On (Screen)
It’s time to test your new class. So, navigate to your project’s WorkSpace
file and fill it in like so:
class WorkSpace: C4CanvasController {
let infiniteScrollView = InfiniteScrollView() //create a new InfiniteScrollview object
override func setup() {
//creates CGRect from canvas frame
//sets frame of infiniteScrollView to match canvas
infiniteScrollView.frame = CGRect(canvas.frame)
//add the view to the canvas
canvas.add(infiniteScrollView)
}
}
This creates a new InfiniteScrollView
variable called infiniteScrollView
and sets its frame to that of the current canvas, to which it’s then added.
You need to convert the canvas frame (a
C4Rect
) to aCGRect
becauseInfiniteScrollView
is a subclass of aUIKit
object which only works with structs likeCGRect
andCGPoint
Continuing on…
Hit:
Build > Run
or
CMD+R
or
the play button at the top-left of the Xcode window.
The simulator should pop up with a seemingly blank screen… There is actually a view on there, but you can’t see it because its background is clear, and you can’t scroll it yet for reasons you’ll get into shortly.
Add Some Content
In order to test your new InfiniteScrollView
, you need to add a few visual indicators to it.
Copy the following and paste it into the main body of your view controller:
func addVisualIndicators() {
//the max number of indicators
let count = 20
//the gap between indicators
let gap = 150.0
//initial offset because we're positioning from the center of each indicator's view
let dx = 40.0
//the calculated total width of the view's contentSize
let width = Double(count) * gap + dx
//create main indicators
for x in 0...count {
//create a center point for the new indicator
let point = C4Point(Double(x) * gap + dx, canvas.center.y)
//create a textshape
let ts = C4TextShape(text: "\(x)")!
ts.center = point
//add it to the canvas
infiniteScrollView.add(ts)
}
}
And, call that method in setup()
like so:
override func setup() {
infiniteScrollView.frame = CGRect(canvas.frame)
canvas.add(infiniteScrollView)
addVisualIndicators()
}
RUN it and you’ll see a bunch of numbers on screen now.
Content + Scrolling
Even though there’s some stuff in infiniteScrollView
you still won’t be able to scroll it because the view doesn’t implicitly know that there’s content. You have to tell it how much there is by updating its contentSize
variable.
Add the following immediately after the for
loop:
infiniteScrollView.contentSize = CGSizeMake(CGFloat(width), 0)
Now that the view knows its max content size, it will scroll. You use 0
for its content height because you don’t need it to scroll vertically.
If you scroll to the end you’ll notice that the last variable hangs a bit… This is because the content size was only an estimate.
To fix this you could either:
- Keep track of the latest origin point
- Figure out where the end of the frame of the last object sits
Let’s update your code to do the latter.
Replace:
infiniteScrollView.contentSize = CGSizeMake(CGFloat(width), 0)
With:
infiniteScrollView.contentSize = CGSizeMake(CGFloat(width+gap), 0)
When you get to the last variable in the for
loop, you calculate the center point of the last shape and add an extra gap
to its x
position. Now, you can see the end of the 20
.
Next, you want to start tracking the contentOffset
.
P.S. don’t worry that the view still bounces, you’ll get to that in a bit…
Content Offset
The way that a UIScrollView
works is essentially like this: a scrollview has a ton more content than the size of its frame, at any given time the user is looking at a “content view” which is somewhere within the bounds of the scrollview’s content.
//image of a content view
What you now want to find out is the location of that content view when a user is scrolling.
Go back to your InfiniteScrollView.swift
file and update layoutSubviews()
to look like this:
override func layoutSubviews() {
//calls UIScrollView's layout method
super.layoutSubviews()
print(contentOffset.x)
}
UIScrollview
has a contentOffset
property, so what you’ve just done is told your app to print that variable’s x
coordinate to the console whenever layoutSubviews()
gets called.
RUN the app and when you scroll it in the simulator you’ll see a bunch of numbers pop up in Xcode’s console.
If you can’t see Xcode’s console, then just hit CMD+SHIFT+C
Or, tap the icon in the top-right of the Xcode window, like this:
You are now tracking the x
position of your scrollview’s contentOffset
.
NOTE: You may have noticed that when you bounce towards the beginning of the content the value of contentOffset.x goes negative… You’re going to use this to your advantage.
Infinite Rules
You now want to apply the rules (above) to your view so that it scrolls infinitely.
Update your layoutSubviews()
function to look like this:
public override func layoutSubviews() {
super.layoutSubviews()
//grab the current content offset (top-left corner)
var curr = contentOffset
//if the x value is less than zero
if curr.x < 0 {
//update x to the end of the scrollview
curr.x = contentSize.width - frame.width
//set the content offset for the view
contentOffset = curr
}
//if the x value is greater than the width - frame width
//(i.e. when the top-right point goes beyond contentSize.width)
else if curr.x >= contentSize.width - frame.width {
//update x to the beginning of the scrollview
curr.x = 0
//set the content offset for the view
contentOffset = curr
}
}
Whenever you go past the edges of your content, in either direction, the view should immediately skip so that it can continue scrolling.
RUN it and try it out for yourself.
Overlap Snap
One thing you should have noticed is that when the view overlaps (i.e. goes from beginning to end, or vice versa), its content makes a very awkward jump…
The solution to this is to attach a copy of the first content view’s contents to the end of your scrollview so that when it snaps it looks seamless.

However, this has absolutely NOTHING to do with the scrollview itself. This trick relies entirely on how you add content to your scrollview.
So, head back over to your WorkSpace
.
Proper Setup
Add a new method called createIndicator()
that you’ll use to create and add text shapes to your scrollview.
Add the following to your WorkSpace
:
func createIndicator(text: String, at point: C4Point) {
//create a textshape
let ts = C4TextShape(text: text)!
//center the shape
ts.center = point
//add it to the canvas
infiniteScrollView.add(ts)
}
This method takes a String
and a C4Point
, creates a new text shape indicator, positions it and adds it to the scrollview.
Simple.
Now, replace the contents of addVisualIndicators
with the following:
func addVisualIndicators() {
//the max number of indicators
let count = 20
//the gap between indicators
let gap = 150.0
//initial offset because we're positioning from the center of each indicator's view
let dx = 40.0
//the calculated total width of the view's contentSize
let width = Double(count + 1) * gap + dx
//create main indicators
for x in 0...count {
//create a center point for the new indicator
let point = C4Point(Double(x) * gap + dx, canvas.center.y)
//create a new indicator
createIndicator("\(x)", at: point)
}
//create additional indicators
var x : Int = 0
//create an offset variable
var offset = dx
//The total width (including the last "view" of the infiniteScrollView is based on the width + screen width
//So, the total width and count of how many "extra" indicators to add is somewhat arbitrary
//This is why we use a while loop
//while the offset is less than the view's width
while offset < Double(infiniteScrollView.frame.size.width) {
//create a center point whose x value starts is the total width + the current offset
let point = C4Point(width + offset, canvas.center.y)
//create the width
createIndicator("\(x)", at: point)
//increase the offset for the next point
offset += gap
//increate x to be used as the variable for the next indicator's number
x++
}
//update infiniteScrollView contentSize
infiniteScrollView.contentSize = CGSizeMake(CGFloat(width) + infiniteScrollView.frame.size.width, 0)
}
Now, everything is nice and smooth.
The view now smoothly loops when you go past the end or the beginning.
Wrapping Up.
You’ve added infinite scrolling to UIKit’s UIScrollView
class, which was the easy part. The magic in getting the effect of infinite scrolling actually depends on how you create and add content to the view itself.
So, you’ve also learned how to add an extra bit of content on to the end of your view so that the effect is seamless when the view snaps into position as it scrolls in any direction.
Getting the extra content positioned properly is usually where the most of your work will take place in setting up an infinite scrollview. Like in this example, if you’re laying out code programmatically, you’ll probably have to make a bunch of tweaks until everything is in the right place.
Code
Here's a final copy of the InfiniteScrollview
class:
