An Example iOS 9 UIPageViewController Application

PreviousTable of ContentsNext
Implementing a Page based iOS 9 Application with UIPageViewControllerWorking with Directories in Swift on iOS 9


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


The previous chapter entitled Implementing a Page based iOS 9 Application with UIPageViewController covered the theory behind implementing page curling view transitions using the UIPageViewController class. This chapter will work through the creation of an application designed to demonstrate this class in action.

The Xcode Page-based Application Template

When creating a new project within the Xcode environment, an option is provided to base the project on the Page-based Application template. When selected, this option generates a project containing an application designed to display a page for each month of the year. This is somewhat strange and something of an anomaly in that this is the only instance where Xcode provides a template that goes beyond providing a basic foundation on which to build and actually provides a sample application. Whilst this is useful for initial learning, unless an application with 12 pages labeled with months of the year is what you need, effort will need to be invested removing existing functionality from the template before it can be used for other purposes.

Rather than use Xcode’s Page-based Application template, this chapter will work through the implementation of page based behavior using the Single View Application template as a starting point. The reasons for this are two-fold. Firstly, implementing UIPageViewController functionality without recourse to the page-based template provides the reader with a better understanding of how the implementation actually works. Secondly, it will typically be quicker to implement the UIPageViewController code by hand than to attempt to repurpose the example application provided by the Page-based Application template.

Creating the Project

Begin by launching Xcode and creating a new iOS Single View Application project with a product name of PageApp, the device menu set to Universal and using Swift as the programming language.


Adding the Content View Controller

The example application will use instances of a single view controller class to display pages to the user. The view will contain a UIWebView object onto which different HTML content will be displayed depending on the currently selected page. The view controller class will also need a data object property that will be used to hold the HTML content for the view.

To add the content view controller, select the Xcode File -> New -> File… menu option and create a new iOS Cocoa Touch class. Configure the class to be a subclass of UIViewController without an XIB file and name the class ContentViewController. On the final screen, select a location for the new class files before clicking on Create.

Select the ContentViewController.swift file from the project navigator panel and add a reference to the data object:

import UIKit

class ContentViewController: UIViewController {

    var dataObject: AnyObject?
.
.
.

Next, select the Main.storyboard file and drag and drop a new View Controller object from the Object Library to the storyboard canvas. Display the Identity Inspector (View -> Utilities -> Show Identity Inspector) and change the Class setting to ContentViewController. In the Identity section beneath the Class setting, specify a Storyboard ID of contentView.

Drag and drop a Web View object from the Object Library to the ContentViewController view in the storyboard canvas and size and position it so that it fills the entire view as illustrated in Figure 37-1. With the Web View object selected in the canvas, use the Auto Layout Pin menu to configure Spacing to nearest neighbor constraints on all four sides of the view with the Constrain to margins option switched off.

Select the Web View object in the storyboard panel, display the Assistant Editor panel and verify that the editor is displaying the contents of the ContentViewController.swift file. Ctrl-click on the web view object and drag to a position just below the class declaration line in the Assistant Editor. Release the line and in the resulting connection dialog establish an outlet connection named webView.


The UI layout for an example iOS 9 UIPageViewController app

Figure 37-1


With the user interface designed, select the ContentViewController.swift file. Each time the user turns a page in the application, the data source methods for a UIPageViewController object are going to create a new instance of our ContentViewController class and set the dataObject property of that instance to the HTML that is to be displayed on the web view object. As such, the viewWillAppear method of ContentViewController needs to assign the value stored in the dataObject property to the web view object. To achieve this behavior, add the viewWillAppear method to assign the HTML to the web view:

import UIKit

class ContentViewController: UIViewController {

    var dataObject: AnyObject?

    @IBOutlet weak var webView: UIWebView!
.
.
.
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        webView.loadHTMLString(dataObject as! String, 
		baseURL: NSURL(string: ""))
    }
.
.
.

At this point work on the content view controller is complete. The next step is to create the data model for the application.

Creating the Data Model

The data model for the application is going to consist of an array object containing a number of string objects, each configured to contain slightly different HTML content. For the purposes of this example, the data source for the UIPageViewController instance will be the application’s ViewController class. This class will, therefore, need references to an NSArray and a UIPageViewController object. It will also be necessary to declare this class as implementing the UIPageViewControllerDataSource and UIPageViewControllerDelegate protocols. Select the ViewController.swift file and add these references as follows:

import UIKit

class ViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {

    var pageController: UIPageViewController?
    var pageContent = NSArray()
.
.
.

The final step in creating the model is to add a method to the ViewController.swift file to add the HTML strings to the array and then call that method from viewDidLoad:

import UIKit

class ViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
.
.
.
    override func viewDidLoad() {
        super.viewDidLoad()
        createContentPages()
    }

    func createContentPages() {

        var pageStrings = [String]()

        for i in 1...11
        {
            let contentString = "<html><head></head><body><br><h1>Chapter \(i)</h1><p>This is the page \(i) of content displayed using UIPageViewController in iOS 9.</p></body></html>"
            pageStrings.append(contentString)
        }
        pageContent = pageStrings
    }
.
.
.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

The application now has a content view controller and a data model from which the content of each page will be extracted by the data source methods. The next logical step, therefore, is to implement those data source methods. As previously outlined in Implementing a Page based iOS 9 Application with UIPageViewController, instances of the UIPageViewController class need a data source. This takes the form of two methods, one of which is required to return the view controller to be displayed after the currently displayed view controller, and the other the view controller to be displayed before the current view controller. Since the ViewController class is going to act as the data source for the page view controller object, these two methods, together with two convenience methods (which we will borrow from the Xcode Page-based Application template) will need to be added to the ViewController.swift file. Begin by adding the two convenience functions:

func viewControllerAtIndex(index: Int) -> ContentViewController? {

    if (pageContent.count == 0) || 
	(index >= pageContent.count) {
        return nil
    }

    let storyBoard = UIStoryboard(name: "Main", 
		bundle: NSBundle.mainBundle())
    let dataViewController = storyBoard.instantiateViewControllerWithIdentifier("contentView") as! ContentViewController

    dataViewController.dataObject = pageContent[index]
    return dataViewController
}

func indexOfViewController(viewController: ContentViewController) -> Int {

    if let dataObject: AnyObject = viewController.dataObject {
        return pageContent.indexOfObject(dataObject)
    } else {
        return NSNotFound
    }
}

The viewControllerAtIndex method begins by checking to see if the page being requested is outside the bounds of available pages by checking if the index reference is zero (the user cannot page back beyond the first page) or greater than the number of items in the pageContent array. In the event that the index value is valid, a new instance of the ContentViewController class is created and the dataObject property set to the contents of the corresponding item in the pageContent array of HTML strings.

Since the view controller is stored in the storyboard file, the following code is used to get a reference to the storyboard and to create a new ContentViewController instance:

let storyBoard = UIStoryboard(name: "Main", 
			bundle: NSBundle.mainBundle())
let dataViewController = 
    storyBoard.instantiateViewControllerWithIdentifier("contentView") 
		as! ContentViewController

The indexOfViewController method is passed a viewController object and is expected to return the index value of the controller. It does this by extracting the dataObject property of the view controller and finding the index of the matching element in the pageContent array.

All that remains to be implemented as far as the data source is concerned are the two data source protocol methods which, in turn, make use of the two convenience methods to return the view controllers before and after the current view controller:

func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {

    var index = indexOfViewController(viewController 
				as! ContentViewController)

    if (index == 0) || (index == NSNotFound) {
        return nil
    }

    index--
    return viewControllerAtIndex(index)
}

func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {

    var index = indexOfViewController(viewController 
	as! ContentViewController)

    if index == NSNotFound {
        return nil
    }

    index++
    if index == pageContent.count {
        return nil
    }
    return viewControllerAtIndex(index)
}

With the data source implemented, the next step is to create and initialize an instance of the UIPageViewController class.

Initializing the UIPageViewController

All that remains is to create the UIPageViewController instance and initialize it appropriately. Since this needs to be performed only once per application invocation a suitable location for this code is the viewDidLoad method of the ViewController class. Select the ViewController.swift file and modify the viewDidLoad method so that it reads as follows:

override func viewDidLoad() {
    super.viewDidLoad()
        
    createContentPages()

    pageController = UIPageViewController(
		transitionStyle: .PageCurl, 
		navigationOrientation: .Horizontal, 
		options: nil)

    pageController?.delegate = self
    pageController?.dataSource = self

    let startingViewController: ContentViewController = 
		viewControllerAtIndex(0)!

    let viewControllers: NSArray = [startingViewController]
    pageController!.setViewControllers(viewControllers 
			as? [UIViewController], 
		direction: .Forward, 
		animated: false, 
		completion: nil)

    self.addChildViewController(pageController!)
    self.view.addSubview(self.pageController!.view)

    let pageViewRect = self.view.bounds  
    pageController!.view.frame = pageViewRect    
    pageController!.didMoveToParentViewController(self)
}

All the code for the application is now complete. Before compiling and running the application some time needs to be taken to deconstruct and analyze the code in the viewDidLoad method.

After constructing the data model with the call to the createContentPage method an instance of the UIPageViewController class is created specifying page curling and horizontal navigation orientation. Since the current class is going to act as the datasource and delegate for the page controller this also needs to be configured:

pageController = UIPageViewController(
	transitionStyle: .PageCurl, 
	navigationOrientation: .Horizontal, 
	options: nil)

pageController?.delegate = self 
pageController?.dataSource = self

Before the first page can be displayed a view controller must first be created. This can be achieved by calling our viewControllerAtIndex convenience method. Once a content view controller has been returned it needs to be assigned to an array object:

let startingViewController: ContentViewController = 
	viewControllerAtIndex(0)!

let viewControllers: NSArray = [startingViewController]

Note that only one content view controller is needed because the page controller is configured to display only one, single sided page at a time. Had the page controller been configured for two pages (with a mid-location spine) or for double sided pages it would have been necessary to create two content view controllers at this point and assign both to the array.

With an array containing the content view controller ready, the array needs to be assigned to the view controller with the navigation direction set to forward mode:

pageController!.setViewControllers(viewControllers 
		as? [UIViewController], 
	direction: .Forward, 
	animated: false, 
	completion: nil)
Finally, the standard steps need to be taken to add the page view controller to the current view. Code is also included to ensure that the pages fill the entire screen:
self.addChildViewController(self.pageController!)
self.view.addSubview(pageController!.view)

var pageViewRect = self.view.bounds
pageController!.view.frame = pageViewRect      
pageController!.didMoveToParentViewController(self)

Running the UIPageViewController Application

Click on the Run button to compile and launch the application in the iOS Simulator or on a physical device. Once loaded, the first content page should appear. A right to left gesture motion on the screen will cause the page to transition to the second page of content and reversing the gesture direction will page backwards:


An example iOS 9 UIPageViewController app running

Figure 37-2

Summary

The goal of this chapter has been to work through an example application designed to implement the page turning transition view provided by the UIPageViewController class.


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book



PreviousTable of ContentsNext
Implementing a Page based iOS 9 Application with UIPageViewControllerWorking with Directories in Swift on iOS 9