UIPageViewController and storyboard UIPageViewController and storyboard ios ios

UIPageViewController and storyboard


2020. Swift updated.

2017 answer...

Nowadays it is dead easy to do this simply using Storyboard.

These sort of "swiping full-screen tutorials" were popular as app "intros" for awhile, so I called the class below IntroPages.

Step 1, make a container view that is a UIPageViewController.

If new to iOS, here is a container view tutorial.

( Note: if you don't know how to "change" the container view to a UIPageViewController, scroll down to the section "How to change..." on that tutorial!

enter image description here )

You can make the container any shape you want. As with any container view, it can be full-screen or a small part of the screen - whatever you want.

Step 2,

Make four straightforward, ordinary, view controllers which can be anything you want - images, text, tables, anything at all. (Purple in the example.)

four controllers

Note that they simply sit there on your storyboard, do not link them to anything.

Step 3, you must Set the IDs of those four pages. "id1", "id2", "id3", "id4" is fine.

set the IDs

Step 4, copy and paste! Here's the class IntroPages,

class IntroPages: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {    var pages = [UIViewController]()    override func viewDidLoad() {        super.viewDidLoad()        self.delegate = self        self.dataSource = self        let p1: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "id1")        let p2: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "id2")        let p3: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "id3")        // etc ...        pages.append(p1)        pages.append(p2)        pages.append(p3)        // etc ...        setViewControllers([p1], direction: UIPageViewController.NavigationDirection.forward, animated: false, completion: nil)    }    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController)-> UIViewController? {               let cur = pages.firstIndex(of: viewController)!        // if you prefer to NOT scroll circularly, simply add here:        // if cur == 0 { return nil }        var prev = (cur - 1) % pages.count        if prev < 0 {            prev = pages.count - 1        }        return pages[prev]    }    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController)-> UIViewController? {                 let cur = pages.firstIndex(of: viewController)!        // if you prefer to NOT scroll circularly, simply add here:        // if cur == (pages.count - 1) { return nil }        let nxt = abs((cur + 1) % pages.count)        return pages[nxt]    }    func presentationIndex(for pageViewController: UIPageViewController)-> Int {        return pages.count    }}

(Look at the comments - there is code for either looping or linear paging as you prefer.)

On the storyboard look at the UIPageViewController. Set the class to be IntroPages.

That's all there is to it - you're done.

You simply set the transition style on the storyboard,

enter image description here

it is very likely you want "Scroll", not the other one.

You're done! And now ...


Introduction to adding UIPageControl ...

You add the UIPageControl in the highest-level wrapper class, "Intro" in the above image example.

(So, surprisingly not in the page view controller, not in "IntroPages".)

Thus, on the storyboard, very simply drag a UIPageControl on to "Intro".

Note! Bizarrely, in storyboards, you cannot move a UIPageControl.

When you drag a page control on to a view controller, it just sits in a fixed place. This is completely bizarre but that's how it is. Don't waste time trying to move it :)

From Intro, you will need to access the page view controller (IntroPages) in the usual way:

class Intro: UIViewController {    @IBOutlet var pageControl: UIPageControl!    var pageViewController: IntroPages!        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {        if segue.identifier == "segueIntroPages" {            // if new to container views, identifier explained here:            // https://stackoverflow.com/a/23403979/294884            pageViewController = (segue.destination as! IntroPages)        }    }        override func viewDidLoad() {        super.viewDidLoad()        view.bringSubviewToFront(pageControl)        pageControl.currentPage = 0    }

Note this key line:

    view.bringSubviewToFront(pageControl)

Add this function

@IBAction func userDidChangePageControl(_ sender: UIPageControl) {}

and then in storyboard click on the page control and drag valueChanged to that function.

The simplest outline version of the function is

@IBAction func userDidChangePageControl(_ sender: UIPageControl) {    pageViewController.setViewControllers(        [pageViewController.pages[newIndex]],        direction: (pageViewController.currentIndex < newIndex)                     ? .forward : .reverse,        animated: true, completion: nil)}

However see this QA Properly solve the new fast-click animation mode / avoid half-page bug for the new UIPageControl as per Apple for a fuller investigation of that.

in IntroPages add the property...

var currentIndex: Int {    if let visibleViewController = viewControllers?.first,       let ci = pages.firstIndex(of: visibleViewController) {        return ci    }    else {        return 0    }}

in IntroPages also add the following, so that, when the user swipes the screen the UIPageControl knows what to do:

func pageViewController(_ pageViewController: UIPageViewController,        didFinishAnimating finished: Bool,       previousViewControllers: [UIViewController],        transitionCompleted completed: Bool) {    (parent as? Intro)?.pageControl.currentPage = currentIndex}

in IntroPages also add the following due to an Apple bug / unusual behavior:

override func viewDidLayoutSubviews() {    super.viewDidLayoutSubviews()    let subViews = view.subviews    var scrollView: UIScrollView? = nil    var pageControl: UIPageControl? = nil        // maintain this code order...    for view in subViews {        if view.isKind(of: UIScrollView.self) {            scrollView = view as? UIScrollView        }        else if view.isKind(of: UIPageControl.self) {            pageControl = view as? UIPageControl        }    }        // maintain this code order...    if (scrollView != nil && pageControl != nil) {        scrollView?.frame = view.bounds        if let pageControl = pageControl {                    view.bringSubviewToFront(pageControl) }    }} 

You're on the way.


For someone, who wants to see working page scroll (forward / backward)

-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController     viewControllerBeforeViewController:(UIViewController *)viewController  {     NSUInteger currentIndex = [myViewControllers indexOfObject:viewController];     // get the index of the current view controller on display     if (currentIndex > 0)     {        return [myViewControllers objectAtIndex:currentIndex-1];        // return the previous viewcontroller     } else     {         return nil;         // do nothing     }  }-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController  {     NSUInteger currentIndex = [myViewControllers indexOfObject:viewController];     // get the index of the current view controller on display     // check if we are at the end and decide if we need to present     // the next viewcontroller     if (currentIndex < [myViewControllers count]-1)     {        return [myViewControllers objectAtIndex:currentIndex+1];        // return the next view controller     } else     {        return nil;        // do nothing     }  }

Just to add to this great answer by EditOR, here's what you do if you prefer "round and around" paging: still using the same technique of EditOR

-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController        viewControllerBeforeViewController:(UIViewController *)viewController    {    NSUInteger currentIndex = [myViewControllers indexOfObject:viewController];    --currentIndex;    currentIndex = currentIndex % (myViewControllers.count);    return [myViewControllers objectAtIndex:currentIndex];    }-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController        viewControllerAfterViewController:(UIViewController *)viewController    {    NSUInteger currentIndex = [myViewControllers indexOfObject:viewController];    ++currentIndex;    currentIndex = currentIndex % (myViewControllers.count);    return [myViewControllers objectAtIndex:currentIndex];    } 


Extending Joe Blow's answer with Swift code for the UIPageViewController class:

import UIKitclass MyPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {    var pages = [UIViewController]()    override func viewDidLoad() {        super.viewDidLoad()        self.delegate = self        self.dataSource = self        let page1: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("page1")        let page2: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("page2")        pages.append(page1)        pages.append(page2)        setViewControllers([page1], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)    }    func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {        let currentIndex = pages.indexOf(viewController)!        let previousIndex = abs((currentIndex - 1) % pages.count)        return pages[previousIndex]    }    func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {        let currentIndex = pages.indexOf(viewController)!        let nextIndex = abs((currentIndex + 1) % pages.count)        return pages[nextIndex]    }    func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {        return pages.count    }    func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {        return 0    }}

Read more on using UIPageViewController with container view with storyboard setup.