Skip to content →

Tag: Interface Builder

Page View Controller in a Container View

Late one evening this week I decided to turn Netflix off and see what I could cobble together with regard to a feature a teammate of mine was working on, namely, using UIPageViewController to provide paginated content.

PageViewControllerDemoAs seen in the animation, for us there are some key characteristics of the targeted implementation. These include:

Swift 3 language and UIKit features illustrated but not specifically discussed include:

  • Protocols
  • Switch case value bindings akin to if let value = value as? SomeType
  • String splitting using map(); substrings
  • UITableViewController and UITableViewControllerDataSource
  • Storyboard and programmatic instantiation of view controllers
  • UIView.animate(withDuration:animations:)

If any of these topics interest you and you want a quick fix, take a look at the project on GitHub, written in Swift 3 (updated for Swift 4). Have specific questions? Keep reading.

FAQ

  • How do I provide paginated content? There’s a storyboard piece and a programmatic piece. In summary, on the storyboard you drop in and hook up a Page View Controller object into your scene flow and add a content object of your choosing. Programmatically, you need only implement a couple UIPageViewControllerDataSource methods … and provide the initial view controller. See PageViewController.swift for that implementation. Details below.
  • How do I use a page scroll rather than curl transition? In the Attributes Inspector for the Page View Controller storyboard scene, set “Transition Style” to “Scroll”.
  • How do I add a page indicator? Implement UIPageViewControllerDataSource methods presentationCount(for:) and presentationIndex(for:); see PageViewController.swift. If you don’t want a page indicator, don’t implement these methods.
  • How do I set the color of the page indicator dots? See PageViewController viewDidLoad():
    let pageControl = UIPageControl.appearance(whenContainedInInstancesOf: [PageViewController.self])
    pageControl.pageIndicatorTintColor = .lightGray
    pageControl.currentPageIndicatorTintColor = .black
    
  • How do I embed a paginated subview? The storyboard is all you need. The process I follow:
    1. Drop a Container View onto the containing view. If you’re good with the tag-along view controller, stop here.
    2. Otherwise, delete the tag-along view controller.
    3. Add the desired controller to the storyboard.
    4. Control-drag from the container to the controller.
    5. Select the “Embed” segue.
  • How do I use a segmented control to switch between subviews? My approach is through manipulation of each subview’s hidden property, accessed by @IBOutlet connection. See MainViewController.swift.
  • How do I reuse a view controller in a storyboard? I recommend embedded Container Views (they’re awesome!). In this project, I have one container view with an Embed segue directly to the content table view controller; then I have a second container view with an Embed segue to the Page View Controller that is programmatically connected to the same table view controller.
  • How do I access a container-embedded view controller? Rather simply…when you know how. The trick is to override prepare(for:sender:) in your container view controller. By doing so, you get access to the destination (embedded) view controller via the provided UIStoryboardSegue. See MainViewController.swift.

Details

Okay, let’s dive into some details. Keep in mind the context of this discussion is the specific implementation as given on GitHub.

Storyboard

Assuming you know the basics of Interface Builder storyboarding, here are some things to be aware of:

  • There are two container views on the project storyboard; although, because they’re stacked, you only see one. The first, named “Paged Container View”, has an embed segue to the Page View Controller scene. The second, “Unpaged Container View”, has an embed segue to Table View Controller.
  • Table View Controller has a Storyboard ID of “TableViewController”, which PageViewController makes use of when serving up paged view controllers.
  • There is no direct connection between Page View Controller and Table View Controller on the storyboard. As just alluded to, that is done programmatically in PageViewController.
  • The Table View Controller has one basic prototype cell matched with a class defined in TableViewController.swift named BasicCell.

Page View Controller

The back end of the Page View Controller storyboard scene is a UIPageViewController-subclassed PageViewController that conforms to the UIPageViewControllerDataSource protocol, much the way UITableViewController now does inherently for its data source and delegate.

At a minimum, a UIPageViewController needs only two things to get paging working:

  1. The plugged-in viewControllerAfter and viewControllerBefore data source methods.
  2. The initial view controller (or view controllers if you’re presenting two pages at at time).

Our page content is presented by a TableViewController. Rather than pre-populating a collection of controllers for all pages, I serve them up on-demand one at a time.

PageViewController maintains no state of its own as concerns which page we’re currently viewing. We leave that to each TableViewController via its section property. In viewDidLoad() we initialize the first Table View Controller’s section to zero, and the page view controller’s data source methods take it from there.

When UIPageViewController asks its data source for the previous or next page, we query the current view controller’s section value to do some bounds checking and initialize the new controller. If within bounds, we return the new controller; otherwise, we return nil.

Table View Controller

The reusability of this controller in two contexts hinges entirely on the optional section property:

  • When non-nil, as set only by the PageViewController, we can know the data is paginated and should only serve up one section, which one being determined by this section value.
  • When nil (i.e., left in its initial state), we know the controller is being used by the directly embedded container view, and needs to serve up data for all sections.

Functionally, this TableViewController is entirely a UITableViewDataSource, providing the bare minimum number of sections, rows per section, section title (not necessary, but useful), and cells. It calls upon an abstracted BasicTableDataModel for those values. This model is assigned by the PageViewController, which it receives from the MainViewController.

Anytime these methods reference the section, they use the sweet nil-coalescing operator to give priority to the optional section property, and failing that, the parameterized section or indexPath value.

Page Indicator

Actually managed within the PageViewController, the page indicator is made possible by implementing two additional UIPageViewControllerDataSource methods: presentationCount(for:) and presentationIndex(for:).

They’re straightforward enough. The only oddity is presentationIndex(for:) is called after viewControllers is initialized to an empty array, but before it is populated. Thus the reason for the nil and count > 0 checks.

Customization of the indicator, that is, its color, is another peculiarity. UIPageControl utilizes an “appearance proxy”, which implements the UIAppearance protocol.

For this project, it comes down to the three lines of code from viewDidLoad(), as given earlier and repeated here:

let pageControl = UIPageControl.appearance(whenContainedInInstancesOf: [PageViewController.self])
pageControl.pageIndicatorTintColor = .lightGray
pageControl.currentPageIndicatorTintColor = .black

If you were only to have one page indicator across your app or want them all to have the same colors, you can replace the first line with:

let pageControl = UIPageControl.appearance()

And, if follows, you can do that anywhere, such as in the AppDelegate.

Basic Table Data Model Protocol

An update to my original implementation addresses my glossing over how model data might work its way into the table view in the real world.

In this update, I add a BasicTableDataModel protocol and two conforming types (LatinTableDataModel and NumericTableDataModel) that present distinct data for use in the paged and non-paged views.

The MainViewController is now the keeper of the models, passing them down to the container view controllers via the segue method prepare(for:sender:). The PageViewController then passes its assigned model down to the individual TableViewControllers it instantiates.

Wrap Up

Adding a page view controller to your project is as simple as:

  1. Dropping a Page View Controller into your storyboard
  2. Connecting to a UIPageViewController-derived class that implements its data source protocol
  3. Employing an approach for serving up paged view controllers that fits with your use case, which really just comes down to providing a view controller with the page-specific model data it needs to display
  4. Optionally, configuring the page indicator by implementing a couple more data source methods and customizing it to a color suitable for your view.

By way of bonus material, you also got to see how to:

  • Reuse and tailor model data for a single view controller in multiple contexts
  • Use a segmented control to switch view contexts within a single controller.

Good stuff. Now, back to Netflix…

10 Comments

Naming Interface Builder Outlets and Actions

Ever wondered how to name an @IBOutlet property or @IBAction method and felt conflicted over “the right way to do it”? You’re not alone:

In case that’s you, here’s the Apple-sanctioned convention:

  • For @IBOutlets, the property name comes in two parts. First, begin with a description of the contents of the control, such as username, passcode, or authenticationNotification. Then add the name of the control, such as Label, TextField, and Button. The result: usernameLabel, usernameTextField, and passcodeButtons. Examples of what not to do: trustAgentUsername: UITextField and authenticationNotification: UILabel.
  • For @IBActions, the method name simply describes the action; there is no control name suffix as with an @IBOutlet. For example, a UIButton action that capitalizes a word in an associated UITextField would be properly declared func capitalizeWord(sender: UIButton). An action that removes a user would be func removeUser(sender: UIButton). Examples of what not to do: didTapButtontappedDone, or overlayTap.

Specific Apple resources prescribing and modeling this convention include their “Start Developing iOS Apps (Swift)” guide, specifically Lesson 3, “Connecting the UI to Code“, and their discussion on Target-Action in the “Concepts in Objective-C Programming” documentation. Their Swift API Design Guidelines say nothing on the matter, presumably deferring to their elsewhere-stated conventions.

A corroborating resource I found is Matt Thompson’s “IB Outlet / IB Action / IB Collection” article. RayWenderlich.com, while also corroborating, has little to say on this specific issue, giving but one example (out of this particular context, even) of an @IBAction between their Objective-C and Swift style guides.

If you have found yourself asking, “How do I name my IB connections?”, hopefully this clears things up for you. It’s not complicated; it’s just a matter of knowing.

Leave a Comment

Container Views

I recently developed a passcode view with core functionality that allows a user to tap in a 6-digit passcode. I then added functionality to support two different modes of operation: new passcode entry and existing passcode entry. I had a single view controller that managed these two modes of operation. The mode was ultimately determined by two different calling view controllers that segued to it.

And it was just too complicated for my tastes.

So I set out this weekend to break it up. I ended up with three controllers: one for the basic passcode-entry functionality, a second for the new-passcode additions, and a third for the existing-passcode additions.

But how to handle this in Interface Builder? Thanks to Xcode 7 and the help of Mike Woelmer, container views was the answer.

Screen Shot 2016-03-05 at 10.29.51 PM
Define Once, Use Twice

As illustrated above, I simply added two additional view controllers to the storyboard, one for each mode of operation, set their Custom Class to one of the new UIViewController subclasses I’d just created, and filled their view with a Container View. Then I embedded the single passcode view controller into each by control-dragging from the container to the passcode controller.

Leave a Comment