14 Comments

How to Move Page Dots in a UIPageViewController

In my last post, I talked about how easy it is to drop in a self-contained UIPageViewController. By popular request, this post covers how to change the position of the UIPageControl dots.

movable dots

Unfortunately, we are unable to alter the position of the UIPageControl embedded inside UIPageViewController directly. Fortunately, we are able to create our own UIPageControl and maintain full control over it.

Setting Up

  1. First, create a new class called TutorialViewController in code.
  2. Drag a new UIViewController object onto the Main.storyboard canvas and set the custom class to TutorialViewController in the identity inspector.
  3. Drag a new container view object onto the newly created view controller and remove the default UIViewController that it created.
  4. Add constraints to pin the container view to all four edges.
  5. Then right-click (or hold Ctrl if you do not have right-click enabled in System Preferences) the container view and drag it on top of our TutorialPageViewController.
  6. Choose “Embed” from the list.
  7. Drag a new UIPageControl object onto the newly created TutorialViewController.
  8. Add constraints to pin it wherever you’d like.
  9. Then add IBOutlets for the page control and container view.
  10. Finally, make sure the new TutorialViewController is the initial view controller.

After completing all of these steps, Main.storyboard should look similar to the following:

Screen Shot 2016-02-03 at 10.30.18 AM

If we build and run now, the page view controller will show up, and you will be able to swipe through pages. However, you will quickly realize that the dots are not updating (and our old dots are still there).

Addressing the Page Dots

In order to remove the old dots, remove the following two UIPageViewControllerDataSource methods:

    presentationCountForPageViewController
    presentationIndexForPageViewController

In order to send data from TutorialPageViewController to TutorialViewController, let’s use a delegate. We have to name the delegate something other than delegate since UIPageViewController already defines delegate. Inside TutorialPageViewController.swift:


class TutorialPageViewController: UIPageViewController {
    
    weak var tutorialDelegate: TutorialPageViewControllerDelegate?

    ...

    override func viewDidLoad() {
        super.viewDidLoad()
        
        dataSource = self
        delegate = self
        
        ...
        
        tutorialDelegate?.tutorialPageViewController(self,
            didUpdatePageCount: orderedViewControllers.count)
    }

    ...

}

...

extension TutorialPageViewController: UIPageViewControllerDelegate {
    
    func pageViewController(pageViewController: UIPageViewController,
        didFinishAnimating finished: Bool,
        previousViewControllers: [UIViewController],
        transitionCompleted completed: Bool) {
        if let firstViewController = viewControllers?.first,
            let index = orderedViewControllers.indexOf(firstViewController) {
                tutorialDelegate?.tutorialPageViewController(self,
                    didUpdatePageIndex: index)
        }
    }
    
}

protocol TutorialPageViewControllerDelegate: class {
    
    /**
     Called when the number of pages is updated.
     
     - parameter tutorialPageViewController: the TutorialPageViewController instance
     - parameter count: the total number of pages.
     */
    func tutorialPageViewController(tutorialPageViewController: TutorialPageViewController,
        didUpdatePageCount count: Int)
    
    /**
     Called when the current index is updated.
     
     - parameter tutorialPageViewController: the TutorialPageViewController instance
     - parameter index: the index of the currently visible page.
     */
    func tutorialPageViewController(tutorialPageViewController: TutorialPageViewController,
        didUpdatePageIndex index: Int)
    
}

Inside TutorialViewController.swift, let’s conform to TutorialPageViewControllerDelegate:


class TutorialViewController: UIViewController {

    @IBOutlet weak var pageControl: UIPageControl!
    @IBOutlet weak var containerView: UIView!
    
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if let tutorialPageViewController = segue.destinationViewController as? TutorialPageViewController {
            tutorialPageViewController.tutorialDelegate = self
        }
    }

}

extension TutorialViewController: TutorialPageViewControllerDelegate {
    
    func tutorialPageViewController(tutorialPageViewController: TutorialPageViewController,
        didUpdatePageCount count: Int) {
        pageControl.numberOfPages = count
    }
    
    func tutorialPageViewController(tutorialPageViewController: TutorialPageViewController,
        didUpdatePageIndex index: Int) {
        pageControl.currentPage = index
    }
    
}

Now that everything is hooked up, go ahead and build and run. Everything should be working as expected! You can download the full source code from this GitHub repo.