Storyboard Updates In Depth Tutorial - Part 3

In this tutorial, you will learn how your existing application project can benefit from Bounces dynamic storyboard updates.

Part 3 - Dealing with Complex View Controller State

Welcome to the third part of this Storyboard Updates In Depth Tutorial! In the two first parts, you have configured an existing PhotoAlbum app project for dynamic storyboard update, you have experienced how dynamic storyboard updates allows for instant testing of storyboards edits in the app running on a device or simulator, and you have learned how to preserve controller state and view state during storyboard updates.

If you have gone through all steps of this tutorial second part, you can reuse the current state of your PhotoAlbums Xcode and Bounces projects. Otherwise you can download the PhotoAlbums project (step 2).

Open the Xcode and Bounces projects and run the PhotoAlbums app.

Adding a Page Controller to the PhotoAlbums app

In the Photo View Controller, it would be nice to navigate to the next or previous photo in the current album with a horizontal swipe gesture.

Currently the app Main storyboard declares four view controllers.

Photo Albums tutorial part 3 - initial storyboard overview

To implement swipe-based photo navigation, UIPageViewController certainly is a good candidate. And to make things easy, you can find a dedicated page view controller for browsing through a photo album already implemented in the project directory at path Extra Source Files/AlbumPageViewController.swift. Add this source file to the PhotoAlbums Xcode project.

Album Page View Controller class definition

In the application view controllers architecture, an Album Page View Controller will be inserted between the Album Content View Controller and the Photo View Controller. This means that an Album Page View Controller will be presented when a photo thumbnail is tapped in the Album Contrent View Controller, and that each page presented by this Album Page View Controller will be a Photo View Controller instance.

Therefore we have to modify the code in AlbumContentViewController.swift to instantiate an AlbumPageViewController, instead of a PhotoViewController, when a collection view cell is selected.

To do so, replace the content of method collectionView(_:, didSelectItemAt indexPath:) with the following code:

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

    if let albumPageController = self.storyboard?.instantiateViewController(identifier: "AlbumPageViewController") as? AlbumPageViewController {
        albumPageController.album = album
        albumPageController.currentPhotoIndex = indexPath.item
        self.navigationController?.pushViewController(albumPageController, animated: true)
    }
}
Album Content View Controller didSelectItem method

Notice that an Album Page View Controller is configured by setting two properties: the current photo album and the index of the selected photo. As you have probably already guessed, these two will constitute the state properties of Album Page View Controller when configuring it for dynamic updates.

Then switch to the Main storyboard, add a Page View Controller and set its class and storyboard id to AlbumPageViewController.

Album Page View Controller added in storyboard

In the Page View Controller attributes, set the transition style to Scroll, run the PhotoAlbums app, and verify that you can navigate between photos in the current album using swipe gestures.

Album Page View Controller configured in storyboard

You can also check that inserting a view controller in the storyboard did not break dynamic update of the Photo View Controller: apply some zooming to the current photo, make an edit to the Photo View Controller in the Main storyboard, and save.

Photo View Controller updated in storyboard and target app

The current Photo View Controller gets updated while keeping its controller state (the currently selected photo) and view state (the visible photo area).

However if you edit the Album Page View Controller in the storyboard, the update is not applied to the running target application. This in not unexpected since we haven't yet configured this view controller for dynamic update.

Configuring the Page Controller for dynamic updates

You should now be pretty familiar with the way to make a view controller dynamic: switch to Bounces, display the Associated Xcode Project Target Configuration pane by clicking on the Xcode Project button in the toolbar, and select the AlbumPageViewController in the Storyboard ViewControllers list.

AlbumPageViewController dynamic configuration in Bounces

We now have to declare the state properties for this view controller. As discussed earlier, these state properties are the controller's album and currentPhotoIndex properties. But currently the Associated Xcode Project Target Configuration interface does not show any candidate state property from the AlbumPageViewController class. And the reason for this is simple: as we have seen in the second part of this tutorial, only properties exposed to the objc runtime can be used as dynamic state properties.

So all we have to do is add an @objc attribute to the properties definition, right? If you try that, Xcode will show an error telling that "Property cannot be marked @objc because its type cannot be represented in Objective-C". Which is the case of most Swift types: classes not derived from an objc class, structs, enums…

Then how can we manage such state properties, since we obviously don't want to change the app model types for the sake of dynamic update?

Fortunately there is a simple and non-obtrusive way: property encapsulation. It consists in adding a controller property with a local type compatible with the objc runtime, that acts as a container of the desired state properties. And if you have installed Bounces code snippets for Xcode, adding a state property container is just a matter of typing a few characters!

Let's see how it works. In the AlbumPageViewController class, type the characters "objc". Xcode proposes various completions for this:

Inserting a state property container: Xcode completions

Since we have two state properties here, select the objcContainer2 snippet. The following code is inserted:

Inserting a state property container: objcContainer2 snippet

Replace the snippet's placeholders with the corresponding state property types and names:
Tip: in Xcode, to replace multiple identical placeholders, you can take advantage multiple selection: click on a placeholder, select every placeholder with the same name by repeating cmd alt E, and then type the replacement string.

  • In place of PropertyContainer, enter "AlbumEntry";
  • in place of Type1, enter "Album" (the album state property type);
  • in place of Type2, enter "Int" (the currentPhotoIndex state property type);
  • in place of property1, enter "album";
  • in place of property2, enter "currentPhotoIndex";
  • in place of statePropertyContainer, enter "albumEntry".

This results in the following type and property definition:

Inserting a state property container: final container property code

The albumEntry property is exposed to the objc runtime, and its getter and setter encapsulate the values of AlbumPageViewController's properties album and currentPhotoIndex.

You can now switch back to the PhotoAlbums Bounces project. albumEntry appears as a candidate state property of the AlbumPageViewController class, so you can select it and complete AlbumPageViewController's dynamic configuration.

AlbumPageViewController state property in Bounces

Dismiss the Associated Xcode Project Target Configuration pane by clicking on the Done button. The project StartModule is updated and it now looks like this:

AlbumPageViewController configured in Bounces

Switch back to Xcode, re-run the PhotoAlbums app and navigate to a Photo View Controller. You are now ready to take advantage of dynamic updates in the Album Page View Controller.

Adding photo navigation buttons

In addition to swipe gestures, we want to add navigation buttons for moving to the next or previous photo in the curent album. These buttons will be positioned in the navigation bar.

In the Main storyboard, add a navigation item to the Album Page View Controller.

Navigation item added to Album Page View Controller in storyboard

Add a bar button item into this navigation bar. By default, Xcode adds it to the Left Bar Button Items, so drag it into the Right Bar Button Items, set a title and a significant image for this button, and save.

Next button added to Album Page View Controller in storyboard

The button appears in the navigation bar, confirming that dynamic updates are correctly configured for our Album Page View Controller.

Connect the button action to Album Page View Controller's action method presentNextPhoto, save the storyboard, and when the update is done, use the button to browse forwards through the photo album.

Next button connected to Album Page View Controller in storyboard

Add another bar button item, configure it and connect its action to the controller's presentPreviousPhoto method.

Previous button added to Album Page View Controller in storyboard

It looks that the buttons order is not the expected one…
Fix this by simply dragging the Previous button after the Next button in the controller's Right Bar Button Items.

Fixed bar buttons order in Album Page View Controller in storyboard

Navigation buttons appear now in the right order and you can use them to browse through photos in the current album.

Wrap up and next step

With this third part of the Storyboard Updates In Depth Tutorial, we have met a more-complex View Controller with several Swift-typed state properties and we've seen how to use property encapsulation to expose state properties to a Bounces execution context, when the direct addition of a @objc attribute is not possible.

If you want to test the PhotoAlbums project in its current state at this point, you can download the PhotoAlbums project (step 3).

In the fourth and final part of this tutorial, we will dynamically add and configure a container-view-based view controller to display the current photo's metadata, and you will learn how you can dynamically add IBOutlets in the Xcode project and use them in your code without having to restart the target application.