UIPageViewController
is a powerful class used by nine out of ten of the mobile apps you will ever come across. Many apps use it for feature lists and/or tips on getting started. In this post, I will show you how incredibly easy it is to make a UIPageViewController tutorial.
To start, let’s create a new application. It doesn’t matter which type you select, since we’re going to start with a clean slate anyway. Select a product name and be sure to select Swift for the language.
Remove all of the auto-generated files besides the ones listed.
Now remove all of the objects in Main.storyboard
. If you did everything right, building the project (Cmd
+ b
) should succeed.
Next, inside Main.storyboard
, add a new Page View Controller
object to the canvas.
Make sure to set the new UIPageViewController
object as the initial view controller in the attributes inspector. That way, it will be initialized when the app launches.
Next, let’s create a custom UIPageViewController
subclass…
…and assign it to the UIPageViewController
object (which we created inside Main.storyboard
earlier) in the identity inspector.
Now for the fun part–let’s create three view controller objects in Main.storyboard.
These will eventually be scrolled through in the page view controller.
Planning ahead a bit, let’s set Storyboard IDs in the identity inspector for each of the view controllers above. These will be used in code to instantiate the view controllers. Alternatively, you could create three separate UIViewController
subclass files and assign them to the objects in the storyboard.
All right, now that our storyboard is all set up, let’s write some code! To start, let’s set ourselves as the datasource and define the required methods.
//
// TutorialPageViewController.swift
// UIPageViewController Post
//
// Created by Jeffrey Burt on 12/11/15.
// Copyright © 2015 Atomic Object. All rights reserved.
//
import UIKit
class TutorialPageViewController: UIPageViewController {
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
}
}
// MARK: UIPageViewControllerDataSource
extension TutorialPageViewController: UIPageViewControllerDataSource {
func pageViewController(pageViewController: UIPageViewController,
viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
return nil
}
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
return nil
}
}
Next, let’s add an array to reference the view controllers we want to page through. The view controllers will be shown in this order.
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newColoredViewController("Green"),
self.newColoredViewController("Red"),
self.newColoredViewController("Blue")]
}()
private func newColoredViewController(color: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewControllerWithIdentifier("\(color)ViewController")
}
Now, it’s time to load up the first view controller (green).
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .Forward,
animated: true,
completion: nil)
}
}
Sweet, green is shown, but what about red and blue? Let’s go ahead and actually implement the UIPageViewControllerDataSource
methods to get swiping up and running.
func pageViewController(pageViewController: UIPageViewController,
viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
guard orderedViewControllersCount != nextIndex else {
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
This gives us the following output:
This is great and all, but what if we wanted to loop the view controllers? Easy! Just convert the UIPageViewControllerDataSource
methods to the following:
func pageViewController(pageViewController: UIPageViewController,
viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
// User is on the first view controller and swiped left to loop to
// the last view controller.
guard previousIndex >= 0 else {
return orderedViewControllers.last
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
// User is on the last view controller and swiped right to loop to
// the first view controller.
guard orderedViewControllersCount != nextIndex else {
return orderedViewControllers.first
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
Sweet, an infinite loop that we actually want!
But we’re making a tutorial. Let’s trash the page curl and replace it with a horizontal scroll. This can be done in the attributes inspector inside Main.storyboard
. Be sure to click on the Tutorial Page View Controller
object since that’s what we’re editing.
There’s only one thing missing: the dots! Simply implement the following two UIPageViewControllerDataSource
methods inside our TutorialPageViewController: UIPageViewControllerDataSource
extension.
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return orderedViewControllers.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
guard let firstViewController = viewControllers?.first,
firstViewControllerIndex = orderedViewControllers.indexOf(firstViewController) else {
return 0
}
return firstViewControllerIndex
}
Build and run and you should be all set! And for the best part…the GitHub Download link!
Update: Be sure to check out my next post, where I explain How to Move Page Dots in a UIPageViewController.
Hi Jeff,
thx for sharing.
Just a question… how I can change the background for dots controllers??
Black is not the ideal color for my pages!
Unfortunately I can’t get the meaning of last two functions!
Thank you in advance,
Michele
Hey Michele!
Thanks for checking out my post. You definitely asked a great question. Unfortunately, there isn’t a
pageControl
property onUIPageViewController
. So, you have to get a little creative. I take advantage ofUIPageControl.appearanceWhenContainedInInstancesOfClasses()
to accomplish this. Here is a snippet:Then, simply call
stylePageControl()
insideviewDidLoad()
.I have also added this to the GitHub repo. Here is the commit in case you’re curious: https://github.com/jeffaburt/UIPageViewController-Post/commit/adc0ae0a6b4d64ca2b0a9c0f2042a188ed673839
Hope this helps!
Jeff
Michele,
I just realized I didn’t respond to your last concern. The last two methods defined inside the
UIPageViewControllerDataSource
extension are directly related to the page control.UIPageViewController
is smart enough to show/hide a page control depending on the presence of these methods. To hide the dots, simply leave off these methods.presentationCountForPageViewController()
tells theUIPageViewController
how many pages there are in total.presentationIndexForPageViewController()
tells theUIPageViewController
which page the user is currently on, which is how the page control knows which dot to color in.Let me know if you have any additional questions/concerns!
Jeff
Hi Jeff,
thanks a lot for you clear answers!
Bye,
Michele
Hey Jeff–thanks for a really handy tutorial! I was wondering though: is it possible to move the location of the dots/bar? Or is the default behavior for the page view controller to present that at the bottom? I tried to set the frame in stylePageControl but it didn’t move.
Hey Dan,
That’s a great question! I am going to cover that in my next blog post. I’ll post a link when it is published. In case you can’t wait, I’ve pushed the necessary code changes to the
master
branch on the GitHub repo.Thanks!
Jeff
Hey Dan,
I’m happy to announce that the blog post went live: https://spin.atomicobject.com/2016/02/11/move-uipageviewcontroller-dots
Let me know if you have any further questions!
-Jeff
Is there a way to set this up for scrolling thru images rather than a series of view controller?
Hello,
Your best bet is probably to use a
UICollectionViewController
.-Jeff
Hey, I was wondering if there was a way of putting eg a “skip” button at the same level as the dots? Similar to this: https://dribbble.com/shots/2140521-TransferApp-Walkthrough
Thanks
Hey Marek,
That’s a great question. I have made those changes to the GitHub repo. Here is a link to the commit.
Let me know if you have any other questions!
-Jeff
Cool, that’s really helpful, I was struggling to implement this!
That is wonderful news. I’m glad I was able to help!
Thanks for article. But I think there is issue with cycle reference/memory leak. When I added initial Navigation Controller and View Controller with button, and returned back from TutorialViewController deinit didn’t called. To fix this need to make tutorialDelegate weak reference and TutorialPageViewControllerDelegate inherit class:
protocol TutorialPageViewControllerDelegate: class {
}
P.S. I used source from GitHub link https://github.com/jeffaburt/UIPageViewController-Post
Hi Igor,
I believe your comment refers to my other blog post: How to Move Page Dots in a UIPageViewController. Anyways, you are absolutely correct. I have made the changes to the GitHub repo as well.
Thanks for checking out the blog!
-Jeff
Hi. Thanks for the tutorial :)
If I have two view controllers already made with names, do I just put those names in the brackets instead of the newViewControllers?:
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newColoredViewController(“Green”),
self.newColoredViewController(“Red”),
self.newColoredViewController(“Blue”)]
}()
Because I tried doing so, and my simulator just shows a black screen.
Hi Cormac,
When you say “two view controllers already made with names,” what exactly do you mean? Are you referring to instantiated view controllers stored in
var
s?Do you see a black screen when using the view controllers from the tutorial (i.e.
self.newColoredViewController("Red")
)?-Jeff
Hi Jeff,
I just wanted to know if there was a way to only show the tutorial the first time the application is run or a way for the user to say do not show tutorial again?
Thanks for the great tutorial!
-Mark
Hey Mark,
Thanks for checking out my post! Anyways, you could use a key inside
NSUserDefaults
to determine if the tutorial has been shown or not. Maybe name it something likedidShowTutorial
(that does not default to true).Hope this helps!
-Jeff
Cool! Thanks for the tip. That idea worked perfectly for what I was trying to do :)
Nice! Happy to have helped. Best of luck to you!
Thank you Jeff for the great tutorial! I’m receiving some errors in my orderViewControllers lazy instkantation. “cannot convert value of type String to expected argument type…XPageViewController” It seems as if the error could be with XCode, have looked over it several times. tips?
Hey Malcolm,
Just to confirm, are you casting
orderedViewControllers
to be of type[UIViewController]
? For example,private(set) lazy var orderedViewControllers: [UIViewController]
. If this isn’t the problem, will you post your entireorderedViewControllers
function?Thanks!
Jeff
Hi Jeff, Firstly, thank you so much for this tutorial. You have saved my bacon and my sanity.
I have one question, that if you could help me with I would have your babies (Not actually offering, plus i’m a dude).
I would like to know how to pass data that I get from the TutorialViewController to the 3 viewcontrollers that are loaded.
I created a StackOverflow question (With a lin to your article) here: http://stackoverflow.com/questions/35681568/pageviewcontroller-pass-variables-to-child-views
P.s I am a little noobie so please forgive me if this is a super basic question.
Hey James,
Great question!
First, I want to thank you for checking out my tutorial and all of the nice things you said about it.
Second, I have a solution for you! I went ahead and https://github.com/jeffaburt/UIPageViewController-Post/commit/2a680583e44435714fce002eeb9bc38dfba93410. I will also post the code here and walk you through it.
(1) Create a
UIViewController
subclass to add custom properties to. For this example, I chose to add aUILabel
since it’s the easiest to view when running the app.(2) Inside
Main.storyboard
, change the custom class for eachUIViewController
“page” toColoredViewController
in the Identity Inspector.(3) Add a
UILabel
to each “page” and constraint it however you’d like. I chose to vertically and horizontally center it in the container. Don’t forget to link theUILabel
toColoredViewController
‘s@IBOutlet weak var label: UILabel!
.(4) Optional: I deleted the default “Label” text in each one that way if we never set the label’s text in code, we will not show “Label” to the user.
(5) We need to do some TLC to
TutorialPageViewController
so it knows thatorderedViewControllers
is now aColoredViewController
array. To make things easy, I’m just going to paste the entire class:(6) Inside
TutorialViewController
: let’s set thelabel.text
. I chose to useviewDidLoad
, but feel free to stuff this logic inside a network request completion block.Hope this helps!
-Jeff
First off, thank you very much for this fabulous resource. I have everything working regarding looping while swiping using the updated code above.
However, the last bit, inside of the “override func viewDidLoad()” , “if let greenColoredViewController …” won’t compile.
I’m receiving the error: “Use of unresolved identifier ‘tutorialPageViewController'”
Thanks, again!
Hey Darrell,
Thanks for checking out the post!
Do you have a
tutorialPageViewController
property onTutorialViewController
?Check out the
pass-data-to-individual-pages
branch on the GitHub repo to compare what I have with what you have.Thanks!
Jeff
Ok, got it all straightened out. This is THE BEST and most flexible implementation I’ve come across. I have, literally, been scouring the net for a couple of days now and am so glad you have taken the time to put this together and freely share your expertise.
Jeff, all the best and keep up the great work!
Darrell,
That is wonderful news! I’m super glad this post has made such an impact. Thanks for the awesome feedback!
Best of luck to you!
Jeff
Hi Jeff,
This is a great tutorial, thanks. I too having been scouring the web for this solution!
Is there a way to use a button on each of the pages to implement Next/Previous rather than the app accepting ‘swipes’.
Apologies if I am missing something obvious.
Jeff, you’ve done so much already and I’ve tried my best to figure this out on my own with the well organized code that you have provided. However, I can’t figure out how to do the following:
I need to reload the individual UIViewControllers when they appear. I completely understand that the (3) views are being cached by the UIPageViewController – and that’s my challenge.
(Any way to disable caching??)
My underlying UIViewControllers each have embedded UITableViews and are all showing up just fine when swiping in either direction.
I’m successfully passing data (NSPredicate values for filtering) to the underlying views. The problem is that the underlying view won’t update because I can’t “reload” it for in order to use the new values.
It would be great if I can somehow say, while viewing the current page: “reload the previous and next pages”, so that when I swipe to them they will update accordingly. Right now all pages are using their initial values, as expected and they’re “stuck” that way.
Maybe this request is outside the scope of your tutorial. If so, I understand. I’ll just keep digging until I can figure it out.
Thanks for listening, Darrell
Hey Darrell,
You can override
viewWillAppear:
on each view controller in question and reload your table view there. It gets called every time you swipe it into view.Let me know how it goes.
Jeff
Hi,
I don’t really have any questions, I just wanted to say thanks for such a great article.
You rock! :)
Ok, got it working! Thanks so much for the tip and the fast reply.
Take care, Darrell
My pleasure! Take care,
Jeff
Thanks for this tutorial, Jeff!
It’s definitely nice to see some handiwork done with Swift’s protocols and extensions. It can be a little difficult to wrap my head around sometimes, but I enjoy the challenge; especially when there are awesome tutorials out there, like this one, to back me up.
My pleasure! Thanks for checking out my post! Best of luck to you
Hey Jeff, Thanks for this tutorial.
It was really helpful. I was wondering how to do this same thing with a root view controller.
Hey Gokulraj,
Thanks for checking out my post! If I understand you correctly, you should be able to drag a
Navigation Controller
Interface Builder object intoMain.storyboard
and setTutorialViewController
as the root view controller.Main.storyboard
should look something like this:I’ve also created a new branch called wrapped-inside-nagivation-controller on the GitHub repo with these additions.
Hope this helps!
Jeff
This is what I was talking about, Jeff.
http://s8.postimg.org/q1bh40a0l/Screen_Shot_2016_03_16_at_7_45_10_PM.png
check this out
Gokulraj,
You can add a container view to your Storyboard’s initial view controller, and link it up with the UIPageViewController. Actually, I did this in my How to Move Page Dots in a UIPageViewController blog post (also linked above). You can also check out the GitHub repo linked above as well, which contains the code you’re looking for.
Here is a screenshot from that post:
Good luck!
Jeff
I have a lot of red dot error message in my codes.
I follow the instruction and i don’t know what went wrong.
Jeff is it possible to have the actual project so i can compare it?
Hey Frank,
Thanks for checking out my post. You can find the GitHub download link at the end of the post, which contains all of the source code. Here it is again for you: https://github.com/jeffaburt/UIPageViewController-Post
Good luck!
-Jeff
thanks jeff!
Jeff
I just wanted to say thank you for this post. It really helped me out.
Hey Mike,
Thanks for the feedback – I really appreciate it. Glad you were able to find my post useful.
Best,
Jeff
Thank you Jeff !
It was a great help for me such as a begginer ios developper!
Ryo,
That is wonderful news. I’m glad you found my post useful.
Best of luck!
Jeff
I’m afraid I’m getting stuck very early. When creating the array of view controllers, I am getting an error “‘TutorialPageViewController’ does not have a member named ‘newColoredViewController’. Any suggestions?
Quit, reboot computer and that problem just went away…
Yeah, that’s pretty weird. Sounds like maybe your Xcode was in an odd state? Anyways, glad your problem went away.
Good luck!
Jeff
Hey Jeff. Awesome tutorial! Quick question, is there any (reasonably) simple way to add in a timer to trigger the segues? Essentially, if the user logs in, the first view auto-segues to the second view after 5 seconds or something? I’m having trouble incorporating a few solutions I’ve found on timers with the above.
I have the below timer formula, but I can’t get a function (moveToNextPage) to work properly with the ImageSwap I’ve already built.
NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: #selector(ImageSwapViewController.moveToNextPage), userInfo: nil, repeats: true)
Any help is greatly appreciated!
Hey Dustin,
Sorry for the delayed response. Were you able to get it? One thing to keep in mind: if the user swipes, will that cancel the timer? Or is user interaction going to be disabled?
Jeff
Jeff –
Very new to this whole process, but I cannot even set the dataSource = self. I am receiving the error “Cannot assign value of type “PageViewTutorial” to type “UIPageViewControllerDataSource?”
import UIKit
class PageViewTutorial: UIPageViewController {
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
}
}
Hi Troy,
Sorry for the delayed response. Were you able to figure it out? If it helps, take a peak at the source code: https://github.com/jeffaburt/UIPageViewController-Post
Let me know if you are still stuck,
Jeff
Hi Jeff! Great Tutorial!
I have a question, if I want to implement the page controller in a game, using spriteKit, How can I do it?
Thanks!
Hey Ana,
Unfortunately, that’s out of scope for this tutorial. Perhaps I will cover that in a future tutorial.
Thanks for checking out my post!
Jeff
Hi Jeff, thanks for the tutorial.
Is it possible to pass the current index count to the TutorialViewController using on the next button?
Hi Jeff, no need for the help. I was able to use pageControl.currentPage as the index. Thanks again for the tutorial.
Hey Dannie,
Glad you were able to figure it out!
Jeff
Hi Jeff,
thx for tutorial.
Just a question… how can i change dots with custom image that deigned with photoshop??
and i complete this tutorial with images. how to make images clickable?
Thank you in advance,
AllPO
Jeff, thanks for the post. Thoughts on the best way to implement with a dynamic number of pages?
Did you actually tried to follow this tutorial ? It’s not working …
Hi Jeff.
First, thanks for tutorial. It helped me out a lot.
Currently, however, I am having a bit of an issue with having a Tab, Navigation Controller overlayed on a pageview, pushing out a view controller with a nested tableview.
As soon as the view is loaded, it’s a couple of pixels below where it should be (the viewcontroller does not fit all the way). Only after I move the view using the page controller functions does it position itself correctly.
I was wondering if you know a potential solution to this.
Thank you
Mr. Burt this tutorial was fantastic.I learned a lot.
Thank you
Hi ,
It is really a nice tutorial and appreciate for your time :)
I want to check if we have a option to see the previous and next viewControllers of the current view controllers in the same screen. I tried multiple options but did not work out. Any suggestions on this is greatly appreciated.
Hi and thanks again for sharing.
Just a quick question.
If I swipe right in the first page (as well I swipe left in the last page) I see a black background… a very bad experience.
How to block the first and last page from this kind of movements?
Hey Jeff,
I am trying to implement page view controller and through your post I am able to do it successfully. Now I want to add a transition between different views with some animation like when my view is swiped then the next view is slight small in size from borders. and when the present view scrolls completely up then the second view restores to fix size.
Can you help?