What You’ll Learn in This Hour:
How to create multiple scenes in the storyboard
The use of segues to transition between scenes
Ways to transfer data between scenes
How to present and use popovers
This hour marks a major milestone in your iOS app development capabilities. In the preceding hour’s lesson, you learned about alert views and action sheets. These were the first user interface (UI) elements we’ve explored that act as (somewhat) independent views that a user interacts with. You’ve also seen how to hide and show views, making it possible to customize your user interface. All of these, however, took place within a single scene. That means that no matter how much was going on onscreen, we used a single view controller and a single initial view to deal with it. In this hour, we break through those limits and introduce the ability to create applications with multiple scenes—in other words, multiple view controllers and multiple views.
In this lesson, you learn how to create new scenes and the new view controller classes you need to back them up. You also learn how to visually define your transitions between scenes and trigger them automatically, or programmatically. In addition, you will explore the use of popovers to present information within a pseudo “window” on the display.
Before we begin, I want to add a disclaimer: In this hour, you learn several different ways to accomplish the same thing. Apple changes the iOS often, and despite their somewhat elegant software development kit (SDK), you will encounter inconsistencies. The takeaway is that you should do what you feel comfortable with. There are plenty of “clever” solutions to problems that result in code that, although correct, is never going to make sense to anyone but the person who wrote it.
Introducing Multiscene Storyboards
We’ve been able to build apps that do quite a few things using a single view, but many don’t lend themselves to a single-view approach. It’s rare to download an app that doesn’t have configuration screens, help screens, or other displays of information that go beyond the initial view that is loaded at runtime.
To use features like these in your apps, you need to create multiple scenes in your storyboard file. Recall that a scene is defined by the presence of a view controller and a view. You’ve been building entire applications in one view with one view controller for the past six hours. Imagine how much functionality you could introduce with unlimited scenes (views and view controllers). With the iOS project storyboard, that’s exactly what you can do.
Not only that, but you can literally “draw” the connections between different scenes. Want to display an information screen if the user touches a Help button? Just drag from your button to a new scene. It “just works.” Figure 11.1 shows a multiscene application design with segues.
The Terminology
Before we head into multiscene development, we should introduce/review a few pieces of terminology, several of which you’ve learned previously but may not have really had to think about until now:
View controller: A class that manages the user’s interactions with his iDevice. In many of the tutorials in this book, single-view controllers are used for most of the application logic, but other types exist (and are used in the coming hours).
View: The visual layout that a user sees onscreen. You’ve been building views in view controllers for quite awhile now.
Scene: A unique combination of view controller and view. Imagine you’re building an image-editing application. You may choose to develop scenes for selecting files to edit, another scene for implementing the editor, another for applying filters, and so on.
Segue: A segue is a transition between scenes, often with a visual transition effect applied.There are multiple types of segues available depending on the type of view controller you’re using.
Exit: The Exit icon appears in each scene in your storyboard. The exit can be used to transition back to a previous scene. If you display four scenes in sequence, for example, and want to move from the fourth back to the first, you would use the first scene’s exit.
Unwind: The process of moving back to an earlier sceneby way of the exit. This is considered an “unwind” segue.
Modal views: A modal view is one that is displayed over top of an original view when user interactions are required. You will mostly be using modal views (by way of the modal segue type) in this book.
Relationship: A “segue” of sorts for certain types of view controllers, such as the tab bar controller.Relationships are created between buttons on a master tab bar that display independent scenes when touched. You learn about these in Hour 13, “Advanced Storyboards Using Navigation and Tab Bar Controllers.”
Storyboard: The file that contains the scene, segue, and relationship definitions for your project.
You must create new class files to support the requirement for multiple view controllers; so, if you need a quick refresher on adding new files to Xcode, refer to Hour 2, “Introduction to Xcode and the iOS Simulator.” Other than that, the only prerequisite is the ability to Control-drag, something you should be very good at by now.
Preparing a Multiscene Project
To create an application with multiple scenes and segues, you must first know how to add new view controller and view pairings to your project. For each of these, you also need supporting class files where you can code up the logic for your additional scenes. To give you a better idea of how this works, let’s use a typical Single View Application template as a starting point.
As you’re well aware, the Single View Application template has a single view controller and a single view (in other words, a single scene). This doesn’t mean, however, that we’re stuck with that configuration. You can expand a single view application to support as many scenes as you want; it just provides us with a convenient starting point.
Adding Additional Scenes to a Storyboard
To add a new scene to a storyboard, open the storyboard file (Main.storyboard) in the Interface Builder (IB) editor. Next, make sure that the Object Library (Control-Option-Command-3) is open and type view controller in the Search field to show the view controller objects that are available, as shown in Figure 11.2.
Next, drag the view controller into an empty portion of IB editor area. The view controller will add itself, with a corresponding view, to your storyboard, and just like that, you’ll have a new scene, as shown in Figure 11.3. You can drag the new view around in the storyboard editor to position it somewhere convenient. By default, the new view controller’s simulated size will not be set. Once you connect it to another view via a segue, however, it automatically inherits that view’s simulated size.
If you find it difficult to grab and drag the new view around in the editor, use the object dock above it. It provides a convenient handle for moving the object around.
Naming Scenes
After adding a new scene, you’ll notice there’s a bit of a problem brewing in the document outline area (Editor, Show Document Outline). By default, each scene is named based on its view controller class. We’ve been using a view controller class called ViewController
, so the document outline shows the default scene as View Controller Scene. Once we add a new scene, it doesn’t have a view controller class assigned yet, so it also appears as View Controller Scene. Add another, and that scene also appears as View Controller Scene (and so on).
To deal with the ambiguity, you have two options: First, you can add and assign view controller classes to the new scenes. We’re going to do this anyway, but sometimes it’s nicer to have a plain English name for a scene that can be anything we want without it reflecting the underlying code. (“John’s Awesome Image Editor Scene” makes a horrible name for a view controller class.) The second option is to label a scene using any arbitrary string you want. To do this, select its view controller in the document outline, and then open the Identity Inspector and expand the Document section, as shown in Figure 11.4. Use the Label field to enter a name for the scene. Xcode automatically tacks Scene onto the end, so there’s no need to add that.
An even faster approach is to select the View Controller line in the Document Outline and press Return; the item is then immediately editable directly in the outline. You can apply this practice to the view controller to set the scene name, or to any objects within the scene to set easy-to-understand labels.
Adding Supporting View Controller Subclasses
After establishing the new scenes in your storyboard, you need to couple them to actual code. In the Single View Application template, the initial view’s view controller is already configured to be an instance of the ViewController
class—implemented by editing the ViewController.swift file. We need to create similar files to support any new scenes we add.
Note
If you’re just adding a scene that displays static content (such as a Help or About page), you don’t need to add a custom subclass. You can use the default class assigned to the scene, UIViewController
, but you won’t be able to add any interactivity.
To add a new subclass of UIViewController
to your project, make sure that the project navigator is visible (Command-1), and then click the + icon at the bottom-left corner of the window. When prompted, choose iOS, the Source category, then Cocoa Touch Class, and click Next. Now, select a subclass of UIViewController
, as shown in Figure 11.5. You’ll also be asked to name your class. Name it something that differentiates it from other view controllers in your project. EditorViewController is better than ViewControllerTwo, for example. Make sure that Language is set to Swift, and then click Next.
Finally, you’re prompted for where to save your new class. Use the group pop-up menu at the bottom of the dialog to choose your main project code group, and then click Create. Your new class is added to the project and ready for coding, but it still isn’t connected to the scene you defined.
To associate a scene’s view controller with the UIViewController
subclass, shift your attention back to the IB editor. Within the document outline, select the view controller line for the new scene, and then open the Identity Inspector (Option-Command-3). In the Custom Class section, use the drop-down menu to select the name of the class you just created (such as EditorViewController
), as shown in Figure 11.6.
After the view controller is assigned to a class, you can develop in the new scene exactly like you developed in the initial scene, but the code will go in your new view controller’s class. This takes us most of the way to creating a multiscene application, but the two scenes are still completely independent. If you develop for the new scene, it’s essentially like developing a new application; there is no way for the scenes to work together and no way to transition between them.
Creating a Segue
Creating a segue between scenes uses the same Control-drag mechanism that you have (hopefully) become very fond of over the first half of this book. For example, consider a two-scene storyboard where you want to add a button to the initial scene that, when clicked, will transition to the second scene. To create this segue, you Control-drag from the button to the second scene’s view controller (targeting either the visual representation of the scene itself, or the view controller line in the document outline), as shown in Figure 11.7.
When you release your mouse button, a Storyboard Segues box appears, as shown in Figure 11.8. Here you can choose the type of segue that you’re creating, most likely Present Modally or Popover Presentation.
A total of five options appear:
Show: Create a chain of scenes where the user can move forward or back. This is used with navigation view controllers, which we look at in Hour 13.
Show Detail: Replace the current scene with another. This is used in some view controllers, such as popular the split-view controller. We’ll look at this in Hour 14, “Navigating Information Using Table Views and Split View Controllers.”
Present Modally: Transition to another scene for the purposes of completing a task. When finished, we dismiss the scene, and it transitions back to the original view. This or the Popover Presentation is the segue you’ll use most often.
Popover Presentation: Displays the scene in a pop-up “window” over top of the current view on some devices (such as the iPad) or as a sliding modal view on others (iPhone).
Custom: Used for programming a custom transition between scenes.
For most projects, you’ll want to choose a modal or popover transition, which is what we use here. The other segues are used in very specific conditions and do not work unless those conditions are met. If that piques your interest, good; you’ll see more of these over the next few hours.
Note: Adaptive Segues
The segues listed here are known as adaptive segues in iOS 8. That means that they’ll adapt to whatever platform they’re running on. While you’ve been targeting iPads or iPhones independently, this “adaptive” nature will make creating universal applications much easier later in the book (and, to be honest, later in this hour—but that’s a surprise).
You’ll see additional segues listed as being “deprecated” when linking your view controllers. These are the “old” (nonadaptive) way of doing things and should be avoided for your shiny new apps.
Tip
You can create a segue that isn’t attached to any particular UI element by Control-dragging from one scene’s view controller to another. Doing so creates a segue that you can trigger, in your code, from a gesture or other event.
After adding the segue to your project, you’ll see a line added to the editor area that visually ties your two scenes together. You can rearrange the individual scenes within the editor to create a layout that maps how the application will flow. This layout is solely for your benefit; it doesn’t change how the application will operate.
You’ll also notice a representation of it in your document outline. The scene that is initiating a segue will show a new line “<Segue name> to <destination>” in the outline. Selecting the segue line gives us the opportunity to configure an identifier (seen in Figure 11.9) and several other settings.
The identifier is an arbitrary string that you can use to trigger a segue manually or identify which segue is underway programmatically (if you have multiple segues configured). Even if you don’t plan to use multiple segues, it’s a good idea to name this something meaningful (toEditor, toGameView, and so on).
The Segue drop-down appears on any segue you’ve added and enables you to switch between segue types at will. Depending on the type of segue you’ve chosen, you’ll have a few additional options to set. Let’s start with the most common type: modal segues.
Configuring Modal Segues
Modal segues offer many different settings for controlling the appearance of the destination view controller as it is displayed on the screen. When viewing a modally presented segue in the Attributes Inspector, you’ll see two drop-downs—presentation and transition, visible in Figure 11.10. These options can dramatically alter how your scenes appear to the user.
As mentioned earlier, segues adapt to the device the application is running on. At present, most of the presentation settings will only result in a display difference on the iPad. The iPad has more screen real estate than an iPhone, so it can do things a little differently. You have four presentation style options:
Full Screen: Sizes the view so that it covers the full screen.
Current Context: Uses the same style display as the scene that is displaying it.
Page Sheet: Sizes the scene so that it is presented in the portrait format.
Form Sheet: Sizes the scene smaller than the screen (regardless of orientation), showing the original scene behind it. This, for all intents and purposes, is the equivalent of a window.
The transition type is a visual animation that is played as iOS moves from one scene to another. You have four options here (as shown in Figure 11.10):
Cover Vertical: The new scene slides up over the old scene.
Flip Horizontal: The view flips around horizontally, revealing the new scene on the “back.”
Cross Dissolve: The old scene fades out while the new scene fades in.
Partial Curl: The old scene curls up like a piece of paper, revealing the new scene underneath.
Caution: Choose Your Styles Carefully!
Not all styles are compatible with all transitions. A page curl, for example, can’t take place on a form sheet that doesn’t completely fill the screen. Attempting to use an incompatible combination will result in a crash. So if you’ve chosen a bad pair, you’ll find out pretty quickly (or you could review the documentation for the transition/style you plan to use).
The default transition—cover vertical—presents the new view by sliding it up over the initial view. This is perfectly acceptable, but I encourage you to try the other transitions for some nifty interactive effects. The partial curl, for example, can be moved with a finger to reveal more content, as demonstrated in Figure 11.11.
Configuring the Popover Segue
Popovers are variation on modal segues that display content on top of an existing view, with a small indicator that points to an onscreen object, such as a button, to provide context. Popovers are everywhere in the iPad interface, from Mail to Safari, as demonstrated in Figure 11.12.
Using a popover enables you to display new information to your users without leaving the screen you are on, and to hide the information when the user is done with it. There are few desktop counterparts to popovers, but they are roughly analogous to tool palettes, inspector panels, and configuration dialogs. In other words, they provide UIs for interacting with content on the screen, but without eating up permanent space in your UI.
What makes a popover different from a modally presented view is that it also requires an additional controller object, a popover controller (UIPopoverPresentationController
). The controller determines the source view of the popover and where it points. When the user is done with the popover, touching outside of its visible rectangle automatically closes the view.
Preparing Popovers
To create a popover, follow the exact same steps as when creating a modally presented segue. Control-drag from the element you want to display a popover to the view controller providing the popover content. When prompted for the type of storyboard segue, as shown in Figure 11.13, choose Popover Presentation.
What makes the popover presentation attractive for any project is that when you create a popover segue and deploy it to the iPhone, it will be presented as a modally presented segue. The adaptive nature of iOS segues mean that the proper interface conventions are followed regardless of the platform. When will this come in handy? When you begin creating applications that run on both iPhones and iPads—which (hint hint) might be sooner than you think.
Setting the Popover Size
The default view associated with a new iPad scene is the same size as the main application interface. When you are displaying a popover, however, the scene needs to be much smaller. Apple allows popovers up to 600 points wide, but recommends that they be kept to 320 points or less. To set the size of the popover, select the view within the popover view controller, and open the Size Inspector (Option-Command-5). Use the Width and Height fields to enter a size for the popover. After you set the size of the view, the scene’s visual representation in the IB editor changes to the appropriate size, as shown in Figure 11.14. This makes building the content view much easier.
Caution: Can’t Set Your Popover Size?
If you find yourself looking at a dimmed-out size setting for the popover view, you probably haven’t yet created the popover segue. It isn’t until Xcode “knows” that you’re adding a popover scene that it unlocks the size settings.
Configuring the Presentation Directions and Passthrough Views
After setting the popover’s size, you want to configure a few attributes on the segue itself. Select the popover segue within the initiating scene, and then open the Attributes Inspector (Option-Command-4), as shown in Figure 11.15.
Within the Storyboard Segue settings, start by setting an identifier for the popover segue. Providing an identifier makes it possible to invoke the popover programmatically, something we look into shortly. Next, choose the directions that the popover’s arrow will appear from; this determines where iOS will present the popover on the screen.
For example, if you only allow a presentation direction of left, the popover displays to the right of whatever object is invoking it.
When a popover is displayed, touching outside of it makes it disappear. If you want to exclude certain UI elements from dismissing the popover, just drag from the Passthrough field to those objects in your view.
By default, a popover’s “anchor” is set when you Control-drag from a UI object to a view controller. The anchor is the object that the popover’s arrow will point to.
As with the modal segue covered earlier, you can create “generic” popover segues that aren’t anchored. Control-drag from the originating view controller to the popover content view controller and choose a popover segue when prompted. We discuss how to display one of these generic popover segues from any button in a few minutes.
That’s all you need to do to create a working popover in IB. Unlike a modal view, a popover is automatically dismissed when you touch outside of it, so you don’t even need a single line of code to create a working interactive popover.
After setting the identifier, style, transition, and presentation for a segue, you’re ready to use it. Without you writing any code, an application that has followed these steps can now present two fully interactive views and transition between them. What it cannot do, however, is interact with them programmatically. In addition, once you transition from one view to another, you cannot transition back. For that, you need some code. Let’s take a look at how you can create and trigger modal segues programmatically, and then the different ways of transitioning back—all of which require some coding.
Presenting Modal Segues Manually
Although it is easy to create segues with a single Control-drag, in several situations you have to interact with them in programmatically. If you create a segue between view controllers that you want to trigger manually, for example, you need to know how to initiate it in code. When users are done with the task in another scene, they also need a mechanism to dismiss the modal scene and transition back to the original scene. Let’s handle these scenarios now.
Starting the Segue
First, to transition to a scene using a segue that you’ve defined in your storyboard, but don’t want to be triggered automatically, you use the UIViewController
instance method performSegueWithIdentifier:sender
. For example, within your initial view controller, you can initiate a segue with the identifier "toMyGame"
using the following line:
performSegueWithIdentifier("toMyGame", sender: self)
That’s it. As soon as the line is executed, the segue starts and the transition occurs. The sender
parameter should be set to the object that initiated the segue. (It doesn’t matter what that object is.) It is made available as a variable property during the segue if your code needs to determine what object started the process.
Dismissing a Modal Scene Programmatically
After users have finished interacting with your view, you’ll probably want to provide them with a means of getting back to where they started. At present, there is no facility in modal segues to allow for this, so you must turn to code. The UIViewController
method dismissViewControllerAnimated:completion
can be used in either the view controller that displayed the modal scene or the modal scene’s view controller to transition back to the original scene:
self.dismissViewControllerAnimated(true, completion: nil)
The completion is an optional closure that will be executed when the transition has completed. You can learn more about closures in Hour 3, “Discovering Swift and the Xcode Playground,” and you should have used a few in the previous hour’s lesson. After you’ve dismissed a scene presented modally, control is returned to the original scene and the user can interact with it as she normally would.
What if you’ve performed several segues and want to jump back to where you started rather than just going back one? For that, you need to make use of exits and unwind segues.
Using Exits (and the Unwind Segue)
Apple has adopted the term unwinding to mean “moving backward in a storyboard.” Storyboards show the path a user takes forward through an application, but (until now) haven’t really shown a means of moving backward. In the preceding section, you learned how to move back to an earlier view controller using dismissViewControllerAnimated:completion
. This will likely be the most common method you use for unwinding, but it’s hardly a flexible solution for jumping back to an arbitrary point in your storyboard.
If you’ve displayed 10 modal view controllers, one after the other, and you want to jump from the tenth back to the second, do you really need to dismiss each view controller from number 10 back to number 2? Not if you make use of exits and the unwind segue.
Preparing a View Controller for an Exit
To use an exit, you must first decide what view controller should allow exits. This is the view controller for the scene that you want to exit to not from. Although this seems a bit counterintuitive to me, just remember that you implement the exit as your destination.
After you’ve made that determination, add a new IBAction
method, shown in Listing 11.1, in the view controller’s swift file.
@IBAction func exitToHere(sender: UIStoryboardSegue) {
// No code needed!
}
There are two unique things to recognize about this method. First, the name of the method can be anything you want, it just needs to have a single UIStoryboardSegue
parameter. Second, you don’t have to add any implementation code to the method. It can remain entirely empty.
Once the method is in place, you can use the Exit icon in your scene.
Connecting to an Exit (Unwind Segue)
To connect to an exit, you follow almost the same process as creating a segue. First, you need something that will trigger the exit (like a button). Control-drag from that object to the Exit icon in the scene you want to exit to, as demonstrated in Figure 11.16.
When you release your mouse button, you’ll be asked to pick from a list of the available exit/unwind methods; choose the exit/unwind method you implemented. You’ll notice that a new unwind segue is added to the scene you are transitioning from, as shown in Figure 11.17.
Once the segue is in place, activating the segue will jump from the activating view controller to the controller with the exit. You can work with the exit/unwind segues exactly like any other segue—including setting an identifier, creating a manual segue (by dragging from a view controller rather than a GUI element), and executing the unwind segue programmatically.
Programming a Segue from Scratch
Xcode storyboarding has made multiscene applications much easier to create than they were in the past, but that doesn’t mean they’re the right choice for all your applications. If you’d rather go the route of programmatically presenting a scene without defining a segue at all, you certainly can. Let’s review the process.
Setting a Storyboard Identifier
After creating your storyboard scenes, but before coding anything, you must provide a storyboard identifier for the view controller you want to display programmatically. This is done by selecting the view controller instance and opening the Identity Inspector (Option-Command-3) in the IB editor. Within the Identity section of the inspector, use the Storyboard ID field to enter a simple string to identify the view controller within your application’s storyboard. Figure 11.18 shows a view controller being configured with the storyboard ID myEditor.
Instantiating the View Controller and View
To display a scene within a storyboard, your application will need to create a UIStoryboard
object using the method storyboardWithName
that references your storyboard file. This can be used to load view controllers and their associated views (that is, scenes).
For example, to create an object mainStoryboard
that references the project’s Main.storyboard file, you could use the following:
let mainStorybord: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
Next, you configure the storyboard object to instantiate the view controller that you want to transition to using the instantiateViewControllerWithIdentifier
method. Assume you’ve created a UIViewController
subclass named EditorViewController
and set the view controller storyboard identifier to "myEditor"
. You can instantiate a new instance of EditorViewController
as follows:
let editorVC: EditorViewController =
mainStorybord.instantiateViewControllerWithIdentifier("myEditor") as
EditorViewController
The EditorViewController
instance, editorVC
, is now ready to be displayed. Before that happens, however, you may want to adjust how it will appear onscreen.
Configuring the Segue Style
Earlier I covered the different transition styles and presentation types that are available for displaying modal scenes. When displaying a view controller manually, you can apply the same effects programmatically by setting the modalTransitionStyle
and modalPresentationStyle
view controller variable properties, respectively. For example, to configure the editorVC
view controller instance, I might use this:
editorVC.modalTransitionStyle=UIModalTransitionStyle.CoverVertical
editorVC.modalPresentationStyle=UIModalPresentationStyle.FormSheet
You can choose from the same transitions and presentation styles as mentioned earlier this hour, but you need to use these constants to identify your selections:
Transition styles: UIModalTransitionStyle.CoverVertical
, UIModalTransitionStyle.FlipHorizontal
, UIModalTransitionStyle.CrossDissolve
, or UIModalTransitionStyle
.PartialCurl
Presentation styles: UIModalPresentationStyle.FormSheet
, UIModalPresentationStyle.PageSheet
, UIModalPresentationStyle.FullScreen
, UIModalPresentationStyle.CurrentContext
, UIModalPresentationStyle.OverFullScreen
, UIModalPresentationStyle.OverCurrentContext
, or UIModalPresentationStyle.Popover
Notice that one of the presentation styles is UIModalPresentationStyle.Popover
? If you set the modalPresentationStyle
to this value, you’ve effectively configured a popover segue. There are a few additional attributes you can use to choose how the popover is displayed; we’ll get to those in a few minutes.
Displaying the View Controller
The final step in programmatically displaying a view is to, well, display it. To do this, use the UIViewController
method presentViewController:animated:completion
from within your application’s initial view controller:
presentViewController(editorVC, animated: true, completion: nil)
The view controller and its associated scene are displayed on the screen using the transition and presentation styles you’ve selected. From here out, you can work with the scene as if it were one you displayed via a segue. You dismiss it using the same dismissViewControllerAnimated:completion
method:
dismissViewControllerAnimated(true, completion: nil)
Note
In this example, we’re programmatically creating a segue to a scene. The methods we use to do this, however, refer to view controllers. Keep in mind that a scene is just a view controller and its associated view. Because we’re instantiating a view controller (with an associated view) from the project’s storyboard, we’re effectively instantiating a “scene.” We then configure the presentation of the view controller/view and display it (the same as a segue).
Although the terminology shifts when working in code, the end result is the same.
Popover Peculiarities
In the previous section, you learned how to create a modal segue (and even configure it as a popover) programmatically—but there are still some peculiarities that you must address when working with popovers. When creating one in Interface Builder, for example, you defined the direction of the popover “arrow” and what object it should point to. To do the same in code, you need to access the UIPopoverPresentationController
—a special object that defines characteristics about the popover’s appearance. When you manually set options for the arrow direction and so on for a popover segue in Interface Builder, you’re actually configuring a UIPopoverPresentationController
that is automatically created for you.
Let’s get back to the example of an “editor” view controller that we want to present modally. Previously we covered the steps to display it as a modal view, but if we want to configure it as popover, we might start with this code:
let editorVC: EditorViewController =
mainStorybord.instantiateViewControllerWithIdentifier("myEditor") as
EditorViewController
editorVC.modalPresentationStyle=UIModalPresentationStyle.Popover
Once we’ve setup the view controller (editorVC
) and set the presentation style to a popover, we can access a UIPopoverPresentationController
that iOS automatically creates for us via the variable property - popoverPresentationController
:
let presentationController:UIPopoverPresentationController =
editorVC.popoverPresentationController!
With access to the presentation controller, we can now control several aspects of the popover display and dismissal process.
Setting the Popover Arrow
To finish the popover presentation, we must determine a few things about our display. First, what object is the popover going to presented from? Any object that you add to a view is a subclass of UIView
, which has a bounds
variable property. Popovers are easily configured to appear from the rectangle determined by an object’s bounds
; as long as you have a reference to the object displaying the popover, you’re set. If you’re triggering the popover from a UI action, the bounds property of the object that triggered the action, for example, is retrieved with this: (sender as UIView).bounds
.
Although Apple’s documentation states that we only need a rectangle or a UIView
(pretty much any onscreen object) to configure the source for a UIPopoverPresentationController
, in practice it does not work. I lamented this fact in the last hour when working with the iPad version of the Alert Sheet style alert. To set the object that the popover will be displayed from, you must set both a sourceRect
and a sourceView
.
Assuming the object you’re presenting is stored in the sender
variable of an IBAction, you might use the following:
presentationController.sourceRect = (sender as UIView).bounds
presentationController.sourceView = sender as UIView
Note
You could certainly cast the sender as the object it really is (such as a UIButton
—we did this in the last hour), but this implementation gives us the flexibility to have any UI object trigger an action and grab its frame
value.
Then, we have determined the popover’s presentation direction. Do this by choosing from these constants:
UIPopoverArrowDirection.Any: The popover can be presented in any direction, giving iOS the greatest flexibility in determining how the popover is displayed.
UIPopoverArrowDirection.Up: The arrow is only displayed pointing up, meaning that the popover appears below the object.
UIPopoverArrowDirection.Down: The arrow is displayed pointing down, and the popover appears above the object.
UIPopoverArrowDirection.Left: The arrow is displayed pointing left, and the popover appears to the right of the object.
UIPopoverArrowDirection.Right: The arrow is displayed pointing right, and the popover appears to the left the object.
Apple recommends that whenever possible you should use the UIPopoverArrowDirection.Any
constant. You can set the popover direction by assigning the constant (or multiple constants separated by a pipe (|)) to the UIPopoverPresentationController
variable attribute permittedArrowDirections
. For example, to present the editorVC
popover with an arrow that points either up or down, you might use the following:
presentationController.permittedArrowDirections=
UIPopoverArrowDirection.Up | UIPopoverArrowDirection.Down
A final display parameter that you need if creating and presenting a popover manually is to set the content size of the popover. This is not set on the presentation controller; it is set by assigning the preferredContentSize variable property on the popover’s view controller. To set a popover size of 320 points wide and 400 points tall, you could type the following:
preferredContentSize = CGSizeMake(320.0, 400.0)
With the size set, the arrow directions configured, and the source location chosen, you can proceed to present the view with the presentViewController
method (just like a “plain” modal segue).
There’s still one more thing that we need to chat about with regards to popover presentation. When a popover is presented, a user can dismiss it by touching outside of it. This isn’t tied to an IBAction, so how can we get ahold of the event of the user dismissing a popover? The answer is through implementing the UIPopoverPresentationControllerDelegate
protocol.
Implementing the UIPopoverPresentationControllerDelegate Protocol
When I first started developing on Apple platforms, I found the terminology painful. It seemed that no matter how easy a concept was to understand, it was surrounded with language that made it appear harder than it was. A protocol, in my opinion, is one of these things.
Protocols define a collection of methods that perform a task. To provide advanced functionality, some classes, such as UIPopoverPresentationController
, may require you to implement methods defined in a related protocol to add certain functionality. Doing this is called conforming to the protocol. Some protocol methods are required and others are optional; it just depends on the features you need.
To deal with the user dismissal of a popover, the class that is responding to the dismissal (usually just a view controller) should conform to the UIPopoverPresentationControllerDelegate
protocol.
To declare that a class, such as a view controller, will be conforming to the UIPopoverPresentationControllerDelegate
protocol, you just modify the class
line in the swift file as follows:
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
Next, you must set the delegate
of the popover presentation controller to the object implementing the protocol. If this is the same object that is creating the popover, and you already have a copy of the presentation controller in presentationController
, you can just use self
, as follows:
presentationController.delegate=self
Now, when the popover is dismissed, the method popoverPresentationControllerDidDismissPopover
will be called from popover’s view controller. All that remains is to implement that method, as demonstrated in in Listing 11.2.
func popoverPresentationControllerDidDismissPopover(popoverPresentationController:
UIPopoverPresentationController) {
// Handle any actions you want executed here.
}
As you can see, it isn’t difficult to work with popovers programmatically, but a bit more setup is required.
Passing Data Between Scenes
You know how to create and display scenes, but there is one very critical piece of the puzzle missing: the ability to share information between the different scenes in your application. Right now, they act as entirely independent applications, which is perfectly fine if that is your intention; however, chances are, you want an integrated user experience. Let’s make that happen.
The most straightforward way for any class to exchange information with any other is through its variable properties and methods. The only trouble with this is that we need to be able to get an instance of one scene’s view controller from another, and, at present, when using a segue we create visually, this process isn’t entirely obvious.
Tip
If you create and display a scene entirely programmatically, as demonstrated in the preceding section, you already have an instance of the new scene’s view controller in your initial view controller. You can set/access variable properties on the new view controller (editorVC.myImportant-VariableProperty=<value>
) before displaying it and after it is dismissed.
The prepareForSegue:sender Method
One way to get references to the view controllers in a segue is by overriding UIViewController prepareForSegue:sender
method. This method is automatically called on the initiating view controller when a segue is about to take place away from it. It returns an instance of UIStoryboardSegue
and the object that initiated the segue. The UIStoryboard
object contains the variable properties sourceViewController
and destinationViewController
, representing the view controller starting the segue (the source) and the view controller about to be displayed (the destination).
Listing 11.3 shows a simple implementation of this approach. In this example, I’m transitioning from my initial view controller (an instance of ViewController
) to a new view controller, which is an instance of a hypothetical EditorViewController
class.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let startingViewController:ViewController =
segue.sourceViewController as ViewController
let destinationViewController:EditorViewController =
segue.destinationViewController as EditorViewController
}
In this implementation, I declare two constants (startingViewController
and destinationViewController
) to reference the source and destination controllers. Then, I assign them to typecast versions of the source and destination variable properties returned by the UIStoryboardSegue
. I have to typecast the view controllers so that Xcode knows what type of object they are; otherwise, I wouldn’t be able to access their variables and methods. Of course, the source view controller is also just self
, so this is a bit of a contrived example.
Once we have a reference to the destination view controller, however, we can set and access variable properties on it, even changing the presentation and transition styles before it is displayed. If it is assigned to a variable property, it can be accessed anywhere within the source view controller.
What if we want the destination view controller to send information back to the source? In this case, only the source can communicate with the destination, because that’s where the prepareForSegue:sender
method is implemented. One option is to create a variable property on the destination controller that stores a reference to the source controller. Another approach, however, is to use built-in variable properties of UIViewController
that make working with modally presented scenes easy, easy, easy.
The Easy Way
The prepareForSegue:sender
gives us a generic way to work with any segue that is taking place in an application, but it doesn’t always represent the easiest way to get a handle on the view controllers involved. For modal segues, the UIViewController
class gives us variable properties that make it easy to reference the source and destination view controllers: presentingViewController
and presentedViewController
.
In other words, we can reference the original (source) view controller within a view controller that has just been displayed by accessing presentingViewController
. Similarly, we can get a reference to the destination view controller from the original controller with presentedViewController
. It’s as easy as that.
For example, assume that the original view controller is an instance of the class ViewController
, and the destination view controller is an instance of EditorViewController
.
From the EditorViewController
, you can access variable properties in the original view controller with the following syntax:
(presentingViewController as ViewController).<variable property>
And within the original view controller, you can manipulate variable properties in the destination view controller with this:
(presentedViewController as EditorViewController).<variable property>
The parentheses with the class name is necessary to typecast presentingViewController
/presentedViewController
to the right object types. Without this notation, Xcode wouldn’t know what types of view controllers these were, and we wouldn’t be able to access their variable properties.
With this data-passing knowledge under our belts, we can go ahead and build our first multi-scene application—and there’s even a surprise—you’re going to make it work on both iPhone and iPad devices.