Why Onboarding is Important
A user has installed your app. But chances are, she will open it and then delete it. This gives you a short window of opportunity to convince her to keep your app around. This is where onboarding comes in.
Onboarding is typically a short slideshow that covers a combination of the following:
- Vision: Convey the value proposition of your app.
- Training: Show how to use your innovative UI.
- Engagement: Walk the user through the sign up process.
Check out UX Archive to see what others have done:
This tutorial will show you how to use a Page View Controller to create an engaging onboarding process.
How to use Page View Controller
Page View Controller is a common tool of choice for onboarding. It’s like a slideshow that pages through View Controllers. You can also use it to create a book or magazine app.
This piece of UI can take a while to get used to. It’s not something you use all the time, except for situations like onboarding. I find myself referring to previous projects every time I implement one of these.
Page View Controller has a DataSource which figures out what pages come before and after the current page. One page is cached in either direction. This means the DataSource only needs to ask about the direction in which you swipe, since it already knows about the trailing direction.
To get a sense of the flow, here’s a sequence of three View Controllers. Pay attention to the blue one in particular:
- You programmatically set the blue View Controller as the starting page.
- The DataSource takes over and stages the green page off screen.
- Swipe to green, and the blue page is cached off screen.
- Swipe again to red, and the blue page is discarded.
1. Download the Starter Project
This tutorial covers a three page onboarding flow. A starter project contains three View Controllers floating around on the Storyboard. Your job is to turn these pages into a slide show using UIPageViewController. You’ll end up with this:
Let’s get started.
2. Set Up the Storyboard
Page View Controller does not use segues. This means you need to add Storyboard IDs to instantiate View Controllers programmatically.
1. Open up the Storyboard. You should see three yellow View Controllers.
2. Drag in a Page View Controller from the Object library onto the Storyboard. It doesn’t matter where.
3. On the Attributes Inspector, change the Transition Style to Scroll.
4. Also check the box for Is Initial View Controller
5. Select the yellow “How to prepare tofu” Scene. On the Identity Inspector, set the Storyboard ID to match the Class name.
6. Likewise, set the Storyboard ID on the other two Scenes.
7. Build and run. You should see a blank screen.
3. Set the Starting Page
The first order of business is to get something into the Page View Controller.
1. Create a new Swift file. Under FileNewFile…, choose iOSSourceSwift File and name it OnboardingPager
2. In the class declaration, subclass UIPageViewController
import UIKit class OnboardingPager : UIPageViewController { }
3. Instantiate StepZero from the Storyboard and set it as the starting page. Your code OnboardingPager.swift file should look like this:
import UIKit class OnboardingPager : UIPageViewController { func getStepZero() -> StepZero { return storyboard!.instantiateViewControllerWithIdentifier("StepZero") as! StepZero } override func viewDidLoad() { setViewControllers([getStepZero()], direction: .Forward, animated: false, completion: nil) } }
instantiateViewControllerWithIdentifier hydrates StepZero from the Storyboard using to the Storyboard ID you set earlier. setViewControllers assigns StepZero as the starting page.
Note: Although setViewControllers accepts an array, you only provide a single View Controller. Only include a second one if you’re creating a page flipping app like iBooks.
4. Back on the StoryBoard, don’t forget to set the Page View Controller class on the Identity Inspector
5. Build and run. You should see the “How to prepare tofu” page.
4. Implement the DataSource
The next step is to incorporate the missing pages.
1. Within viewDidLoad, set the DataSource to self.
dataSource = self
You’ll get a compiler error until you conform to the DataSource protocol.
2. Implement the DataSource protocol:
extension OnboardingPager : UIPageViewControllerDataSource { func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? { return nil } func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? { return nil } }
This boilerplate code suppresses the compiler error, but doesn’t do anything yet.
3. Make helper methods to instantiate the other two pages from the Storyboard
func getStepOne() -> StepOne { return storyboard!.instantiateViewControllerWithIdentifier("StepOne") as! StepOne } func getStepTwo() -> StepTwo { return storyboard!.instantiateViewControllerWithIdentifier("StepTwo") as! StepTwo }
These are similar to the getStepZero method you implemented earlier.
4. In the DataSource, return the correct page as the user swipes forward.
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? { if viewController.isKindOfClass(StepZero) { // 0 -> 1 return getStepOne() } else if viewController.isKindOfClass(StepOne) { // 1 -> 2 return getStepTwo() } else { // 2 -> end of the road return nil } }
The logic is fairly straightforward. If the user is on:
- StepZero: return StepOne
- StepOne: return StepTwo
- StepTwo: return nil to prevent further scrolling
Feel free to refactor this into something more functional and elegant.
5. Do the same for when the user swipes backward.
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? { if viewController.isKindOfClass(StepTwo) { // 2 -> 1 return getStepOne() } else if viewController.isKindOfClass(StepOne) { // 1 -> 0 return getStepZero() } else { // 0 -> end of the road return nil } }
The code in viewControllerBeforeViewController is similar to viewControllerAfterViewController, except you count down instead of up.
Your OnboardingPager.swift file should look like this:
import UIKit class OnboardingPager : UIPageViewController { func getStepZero() -> StepZero { return storyboard!.instantiateViewControllerWithIdentifier("StepZero") as! StepZero } func getStepOne() -> StepOne { return storyboard!.instantiateViewControllerWithIdentifier("StepOne") as! StepOne } func getStepTwo() -> StepTwo { return storyboard!.instantiateViewControllerWithIdentifier("StepTwo") as! StepTwo } override func viewDidLoad() { dataSource = self setViewControllers([getStepZero()], direction: .Forward, animated: false, completion: nil) } } extension OnboardingPager : UIPageViewControllerDataSource { func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? { if viewController.isKindOfClass(StepTwo) { return getStepOne() } else if viewController.isKindOfClass(StepOne) { return getStepZero() } else { return nil } } func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? { if viewController.isKindOfClass(StepZero) { return getStepOne() } else if viewController.isKindOfClass(StepOne) { return getStepTwo() } else { return nil } } }
6. Build and run. You should see all three pages as you scroll forward and backward, similar to the end result (but without the paging dots).
5. Add a Page Control
Got dots? Page View Controller comes with a built-in Page Control. You just need to tell it the number of dots, and which dot is your starting point.
1. Within the DataSource, implement presentationCountForPageViewController.
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int { return 3 }
Three pages means three dots.
2. Also implement presentationIndexForPageViewController.
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int { return 0 }
This starts you at the first dot when you call setViewControllers.
Note: presentationIndexForPageViewController does not get called each time you swipe. It only comes into play when you call setViewControllers.
3. In viewDidLoad, set the color behind the page dots
view.backgroundColor = .darkGrayColor()
Note: You’re not setting the background color on the dots, but on the Page View Controller. With the built-in implementation, pages no longer take up the full height of the screen — rather, Page View Controller exposes a region of itself on the bottom dedicated to the Page Control.
The final version of OnboardingPager.swift should look like this:
import UIKit class OnboardingPager : UIPageViewController { func getStepZero() -> StepZero { return storyboard!.instantiateViewControllerWithIdentifier("StepZero") as! StepZero } func getStepOne() -> StepOne { return storyboard!.instantiateViewControllerWithIdentifier("StepOne") as! StepOne } func getStepTwo() -> StepTwo { return storyboard!.instantiateViewControllerWithIdentifier("StepTwo") as! StepTwo } override func viewDidLoad() { view.backgroundColor = .darkGrayColor() dataSource = self setViewControllers([getStepZero()], direction: .Forward, animated: false, completion: nil) } } extension OnboardingPager : UIPageViewControllerDataSource { func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? { if viewController.isKindOfClass(StepTwo) { return getStepOne() } else if viewController.isKindOfClass(StepOne) { return getStepZero() } else { return nil } } func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? { if viewController.isKindOfClass(StepZero) { return getStepOne() } else if viewController.isKindOfClass(StepOne) { return getStepTwo() } else { return nil } } func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int { return 3 } func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int { return 0 } }
4. Build and run. It should look like the animated GIF at the top of the page.
More Information
You can download the completed project here.
To learn more about Page View Controller, here’s a good discussion on StackOverflow.
A lot of thought goes into onboarding. At a recent meetup in DC, Levent Gurses presented this handy slide deck on the subject. Also, the WWDC 2014 video Making a Great First Impression With Strong Onboarding Design is a good resource.
Although it’s out of the scope of this tutorial, animations are often used with onboarding to get the message across with fewer words. If you have a subscription at Ray Wenderlich, Marin Todorov made a great video series on iOS Animation with Swift.
Have any suggestions for future tutorial topics? Feel free to add your thoughts to the comments.
Like this post? Please share it below! Then follow us on Twitter @thorntech or join our mailing list for future updates.