The Swift Swift Tutorials: Adding Modal Views and Popovers

[Updated to Swift 2.0/iOS9 9/29/15]
There are times your user interface needs to grab attention for a control. This is what modal views and popovers are. Modal views are one of the oldest of the view controllers. Some early applications on the iPhone were completely modal views — some still do.  Modals do not do their own housekeeping like navigation controllers or tab controllers, so they are not as efficient for the backbone of your application. Since they need the user attention and suspend everything else in your UI, they are good for settings pages and the like.

Developed originally for the iPad, Popovers are similar to modals. They are a type of view controller that require user attention. Modals take over the window, which on the bigger real estate of the iPad, is often a waste of space. Popovers use limited space and place a smaller “window” on top of the main window.

In this lesson we’ll show several ways to implement popovers and modals.

Set Up the Project

First we’ll need a new project. In Xcode 6, go to File>New>Project… or hit Command-Shift-N. Create a Single-View project called SwiftPizzaPopover, set the language to Swift. Set the device to Universal, then save the project to a place you’d like.
We will be using some photos. You can use your own, here are the two I used:
pizza_sm

popover_sm

These are the full-size images. Right-Click and save the images from your browser to some folder or your desktop on your Mac. In Xcode, click on the images.assets, and from the folder you saved the images drag them into the assets work area.

Go into the storyboard, and drag four buttons on the scene. On the two top buttons, remove the title and add the pizza and popover images we just loaded as the button image. On the button below the pizza image button, change the title to Pizza Modal and set the font size to 20. Change the title of the button to Popover but and set the font size to 20. Using whatever method you’d like ( I used some Auto Layout), arrange the buttons to something like this:

2015-09-29_11-02-55

Now drag two more view controllers out. Change to the media library by selecting the clip of film in the lower right. In the first view controller, add an UImageView of the pizza by dragging the pizza image to one of the new controllers. Do the same with the popover image in the other controller. If you use auto layout,pin them to the upper left corner

Click the square-in-circle button to go back to the object library, and drag a label to each of these view controllers. Change the text to read BBQ Chicken Pizza and Cheddar Popover respectively. Make the font for each 20 points. If you use Autolayout, I center aligned them. For the pizza view controller, click the back ground of the controller. Click the color strip in the background color property for the controller, In the color picker that appears, click the sliders button if not already pressed. Change the color to red 255, green 0, and blue 222. Do the same for the popover, except change the color to red 222, green 222, and blue 0.

Unlike Navigation controllers, you need to explicitly dismiss a modal controller.  We’ll need a done button. Add a button to the modal view, labeled Done at 22 points font size.Make the background red with a whtie text. Popovers dismiss when anywhere outside the popover gets a touch event. However it might be handy to know how to dismiss a popover, so cut and paste this button to the popover view. I pinned these with auto layout to the bottom of the scene.

Hit Command-n to get the New File dialog. Under iOS click Source> Cocoa Touch Class. Create a new view controller subclassing UIViewController called PizzaModalVC. Repeat and make a UIViewController subclass called PopoverVC Assign the subclasses to their respective view controllers using the identity inspector.
When done your two controllers should look like this:

2015-09-29_11-12-37

Your story board should look like this:

2015-09-29_11-20-16

Using Segues Directly

The easiest way of using segues and modals is to directly connect a segue from the button to the view controller. Get into Story board. From the pizza picture button, control-drag to the PizzaModalVC view. In the pop up menu, select Present Modally. Click on the circle for the segue and set the identifier to Pizza. Now for the popover, control drag from the popover photo to the PopoverVC view. Select popover presentation. Select the segue, then set the identifier to Popover.

Select an iPad Air 2 in the simulator. Build and run.

2015-09-29_11-34-17

Click on the Popover picture. You will get this:

2015-09-29_11-35-38

Tap anywhere but the popover and the popover disappears.

Now tap the pizza picture, and the pizza appears from the bottom. However, we can’t get rid of it.

2015-09-29_11-36-13

We did not yet make the dismissal code. Stop the simulator, and go back to the storyboard. Bring up the assistant editor, and select the PizzaModalVC view. Connect the done button to the view controller code by selecting the done button and then control dragging the button to the view controller. I usually stick my target actions at the top of the class, above viewDidLoad. Select an action for the connection as a UIButton. Call this pizzaModalDone Do the same for the popover, calling the method popoverDone.

In the PizzaModalVC class add the following to pizzaModalDone:

dismissViewControllerAnimated(true, completion: nil)

In the PopoverVC class, add the following to popoverDone:

dismissViewControllerAnimated(true, completion: nil)

Build and run. Now both Done buttons work, and well as clicking in the shaded area outside the popover.

Calling a Segue Programmatically

Go back to the storyboard. In our main view controller add this above viewDidLoad():

@IBAction func openPizzaModal(sender: UIButton) {
        performSegueWithIdentifier("Pizza", sender: self)
    }
    @IBAction func openPopover(sender:UIButton){
        performSegueWithIdentifier("Popover", sender: self)
    }

Connect the openPizzaModal(sender:) to the Pizza Modal button and the openPoppover(sender:) to the Popover button by dragging from the circle to the correct button. Build and run. Now you can use the bottom text buttons as well as the photos. Line 2 and 5 use the performSegueWithIdentifier() method. When we set up our segues we gave them identifiers. Once set up, more than the buttons we connected can use the segue, as in this case. When you have two buttons, this is a good way of getting the same behavior out of them. If you want all your buttons to use segues programmatically, control drag from the source view controller icon in the storyboard to the destination view controller.

Programmatically Calling a Modal View Controller

Some times we need to code modal view controllers. Press Command-n to make a new view controller called PizzaModalProgVC subclassing UIViewController. Add the following properties just after the class declaration.

let dismissButton:UIButton! = UIButton(type:.Custom) 
let myImage:UIImage! = UIImage(named:"pizza_sm")
let myLabel = UILabel()

It is a good practice to keep UI as properties the class can use, unless you are sure you will do nothing with it. Think of it as a strong version of an outlet or action. Anything you will need an outlet or action for should be a property when writing programmatically. Make a view by changing viewDidLoad to this:

override func viewDidLoad() {
    super.viewDidLoad()

    //set our transition style
    modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
    // Build a programmatic view
    view.backgroundColor = UIColor(
        red: 0.8,
        green: 0.5,
        blue: 0.2,
        alpha: 1.0)
        if (myImage != nil){
            let myImageView = UIImageView(image: myImage)
            myImageView.frame = view.frame
            myImageView.frame = CGRectMake(10, 10, 200, 200)
            view.addSubview(myImageView)
            }else{
                println("image not found")
            }

            myLabel.text = "BBQ Chicken Pizza!"
            myLabel.frame = CGRectMake(220, 10, 300, 50)
            myLabel.font = UIFont(
                name: "Helvetica",
                size: 24)
            myLabel.textAlignment = .Left
            view.addSubview(myLabel)

            dismissButton.setTitle("Done",
                forState: .Normal)
            dismissButton.titleLabel!.font = UIFont(
                name: "Helvetica",
                size: 24)
            dismissButton.titleLabel!.textAlignment = .Left
            dismissButton.frame = CGRectMake(150,175,200,50)
            dismissButton.addTarget(self,
               action: "pizzaDidFinish",
               forControlEvents: .TouchUpInside)
            view.addSubview(dismissButton)
    }

I covered most of the view programming in another tutorial. For compactness, I’m leaving out auto layout and setting the position by setting the frame. This is different enough so we can tell the difference between our two Pizza controllers.

All view controllers have a property modalTransitionStyle which sets the style of the modal transition. To show we are doing a different modal view, I set the transition to FlipHorizontal instead of its default CoverVertical.

We still need a target for the done button, so add:

func pizzaDidFinish(){
     dismissViewControllerAnimated(true,
         completion: nil)
}

As we did in the segue case, we added a dismissal. View controllers dismiss themselves. Now we have a class to call. Go back to ViewController.swift and change openPizzaModal to this:

@IBAction func openPizzaModal(sender: UIButton) {
//performSegueWithIdentifier("Pizza", sender: self)
presentViewController(PizzaModalProgVC(),
    animated: true, 
    completion: nil)
}

This time we will call presentViewController and add the view controller we just made PizzaModalProgVC. We initialize it right here in the method, saving us extra lines of declaration code.

Build and run. Select the Pizza Modal button and you will see our flip transition and a different layout than the segue version.

2015-09-29_11-43-32

Programmatically Calling a Popover

The subclass that calls a popover looks pretty much the same as a modal. Select Command-n again. Make the PopoverProgVC subclass of UIViewController look like this:

class PopoverProgVC: UIViewController {
    let dismissButton:UIButton! = UIButton.(type:UIButtonType.Custom)
    let myImage:UIImage! = UIImage(named:"popover_sm")

    func pizzaDidFinish(){
        dismissViewControllerAnimated(true,
            completion: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Build a programmatic view
        view.backgroundColor = UIColor(
            red: 0.8,
            green: 0.5, 
            blue: 0.2, 
            alpha: 1.0)

        if (myImage != nil){
            let myImageView = UIImageView(image: myImage)
            myImageView.frame = view.frame
            myImageView.frame = CGRectMake(10, 10, 200, 200)
            view.addSubview(myImageView)
        }else{
            println("image not found")
        }
        let myLabel = UILabel()
        myLabel.text = "Cheddar Popover"
        myLabel.frame = CGRectMake(10, 250, 300, 50)
        myLabel.font = UIFont(
            name: "Helvetica",
            size: 24)
        myLabel.textAlignment = .Left
        view.addSubview(myLabel)

        dismissButton.setTitle("Done",
            forState: .Normal)
        dismissButton.titleLabel!.font = UIFont(name: "Helvetica", size: 24)
        dismissButton.titleLabel!.textAlignment = .Left
        dismissButton.frame = CGRectMake(150,175,200,50)
        dismissButton.addTarget(self,
            action: "pizzaDidFinish",
            forControlEvents: .TouchUpInside)
        view.addSubview(dismissButton)
    }
}

We call a popover differently though. Change the view controller method as follows:

@IBAction func openPopover(sender:UIButton){
    //performSegueWithIdentifier("Popover", sender: self)
    let vc = PopoverProgVC()
    vc.modalPresentationStyle = .Popover
    presentViewController(vc, animated: true, completion: nil)
    vc.popoverPresentationController?.sourceView = view
    vc.popoverPresentationController?.sourceRect = sender.frame      
}

There are two methods to present a popover, one for popovers originating in toolbar buttons and one which presents a popover attached to a CGRect and displayed in some view. Both start the same. We set a modal presentation style of .Popover, then present the popover like the modal view we did for the pizza.

The difference is in the next two lines, which must come immediately after the presentation. These two lines of code set up the second kind of popover, one that anchors to a CGRect and presents itself in the space of a UIView. When we use a popover to present, we get a popoverPresentationController on the modal view. This controls the appearance and presentation of the popover. The property sourceView of code>popoverPresentationController indicates the view that the popover will appear, which often is view. The property sourceRect indicates the CGRect that the popover will be anchored to. In this example I attached the popover to the button that opens it, which is a common occurrence.

Now build and run. Select the popover button, and it works.

2015-09-29_11-43-00

Often Popovers appear from bar button items in a toolbar or navigation bar. While we need two properties in other cases, for a bar button item you need only one, appropriately named barButtonItem. Though we do not use it on our project, an equivalent code for using a bar button item might be this:

@IBAction func openPopover(sender:UIBarButtonItem){
    let vc = PopoverProgVC()
    vc.modalPresentationStyle = .Popover
    presentViewController(vc, animated: true, completion: nil)
    vc.popoverPresentationController?.barButtonItem = sender      
}

Size Classes, Modals, and Popovers

Prior to iOS8, popovers and modals were different. Starting with iOS8, popovers became a type of modal. This was due to size classes. While I go into the details about size classes in my books Swift Swift View Controllers and Practical Autolayout, the basics of size classes is that your layout changes and adapts to different size screens and orientations automatically.

Before iOS8, if you ran a popover on any iPhone, you would get an error. Now iOS just adapts. For most phones in most orientations you get a .Fullscreen modal presentation. Try it. Select an iPhone 5s in the simulator, and run the app again. Select a popover and you get a modal:

2015-09-29_12-21-01

The iPhone 6 plus and 6s plus in landscape will give you a .Formsheet presentation style. Try it there as well.

2015-09-29_12-13-32

Starting with iOS9 on iPads, we also have mutitasking, which will act like an iPhone for popovers. Run the simulator With an iPad air 2. I rotated my screen for better effect by pressing Command-right arrow:

2015-09-29_12-00-37

With the simulator running, hit command-Shift-H. to get back to the home screen. Select safari as an app. Drag from the right edge of the screen towards the center. In the icons that appear, pick the Modal demo, then once loaded, click the modal once again.

2015-09-29_12-02-53

On the left you will have Safari, on the right our App, though compressed. Try the popover again and you get a .Fullsheet popover.

Drag the white bar to the left of our app, and the bar turns black. Move it to the center. We still get a full Sheet.

2015-09-29_12-03-07

Instead of having to figure out any of these cases, iOS does it for you.

We’ve covered the basics of popovers and modals. Modals work anywhere, on iPhone or iPad. Popovers and some special case modals work or appear differently on iPad only. In this lesson we saw how similar popovers are to modals. There is a few more things to do with modals which we will still need to get to. In future posts, we’ll discuss the one popover bug that will definitely get you kicked out of the app store, and how to avoid it.

The Whole Code

//
// ViewController.swift
// SwiftPizzaPopover
//
// Created by Steven Lipton on 8/27/14.
// Copyright (c) 2014 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
@IBOutlet weak var openPopover: UIButton!
/* if you are paying attention, here is the code for a popover bar button 
// and how to comply with the single popover rule. 
    var currentPopover:UIPopoverController! = nil


    @IBAction func showBarPopoverButton(sender: UIBarButtonItem) {
        if presentedViewController != nil { //how to avoid that
                                            // error I mentioned in the conclusion
            presentedViewController?.dismissViewControllerAnimated(true, 
                 completion: nil)
        }
        let vc = PopoverProgVC()
        vc.modalPresentationStyle = .Popover
        presentViewController(vc, animated: true, completion: nil)
        vc.popoverPresentationController?.barButtonItem = sender
*/

@IBAction func openPopover(sender:UIButton){
//performSegueWithIdentifier("Popover", sender: self)
    let vc = PopoverProgVC()
    vc.modalPresentationStyle = .Popover
    presentViewController(vc,
       animated: true,
       completion: nil)
    vc.popoverPresentationController?.sourceView = view
    vc.popoverPresentationController?.sourceRect = sender.frame
}

@IBAction func openPizzaModal(sender: UIButton) {
    //performSegueWithIdentifier("Pizza", sender: self)
    presentViewController(PizzaModalProgVC(), animated: true, completion: nil)

}

}

//
// PizzaModalVC.swift
// SwiftPizzaPopover
//
// Created by Steven Lipton on 8/27/14.
// Copyright (c) 2014 MakeAppPie.Com. All rights reserved.
//

import UIKit

class PizzaModalVC: UIViewController {

@IBAction func pizzaModalDone(sender: UIButton) {
dismissViewControllerAnimated(true, completion: nil)
}

}

//
// PopoverVC.swift
// SwiftPizzaPopover
//
// Created by Steven Lipton on 8/27/14.
// Copyright (c) 2014 MakeAppPie.Com. All rights reserved.
//

import UIKit

class PopoverVC: UIViewController {
    @IBAction func popoverDone(sender: UIButton) {
        dismissViewControllerAnimated(true, completion: nil)
    }
}
//
// PizzaModalProgVC.swift
// SwiftPizzaPopover
//
// Created by Steven Lipton on 8/28/14.
// Copyright (c) 2014 MakeAppPie.Com. All rights reserved.
//

import UIKit

class PizzaModalProgVC: UIViewController {
    let dismissButton:UIButton! = UIButton(type:.Custom)  
    let myImage:UIImage! = UIImage(named:"pizza_sm")

    func pizzaDidFinish(){
        dismissViewControllerAnimated(true, completion: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

    //set our transition style
    modalTransitionStyle = .FlipHorizontal

// Build a programmatic view
    view.backgroundColor = UIColor(
        red: 0.8,
        green: 0.5,
        blue: 0.2,
        alpha: 1.0)
//add the image
    if myImage != nil{
        let myImageView = UIImageView(image: myImage)
        myImageView.frame = view.frame
        myImageView.frame = CGRectMake(10, 10, 200, 200)
        view.addSubview(myImageView)
    }else{
        print("image not found")
    }
//add the label
    let myLabel = UILabel()
    myLabel.text = "BBQ Chicken Pizza!"
    myLabel.frame = CGRectMake(220, 10, 300, 50)
    myLabel.font = UIFont(
        name: "Helvetica", 
        size: 24)
   myLabel.textAlignment = .Left
   view.addSubview(myLabel)
//add the done button
    dismissButton.setTitle("Done",
        forState: .Normal)
    dismissButton.titleLabel.font = UIFont(
        name: "Helvetica", 
        size: 24)
    dismissButton.titleLabel.textAlignment = .Left
    dismissButton.frame = CGRectMake(150,175,200,50)
    dismissButton.addTarget(self,
        action: "pizzaDidFinish", 
        forControlEvents: .TouchUpInside)
    view.addSubview(dismissButton)
}

}

//
// PopoverProgVC.swift
// SwiftPizzaPopover
//
// Created by Steven Lipton on 8/28/14.
// Copyright (c) 2014 MakeAppPie.Com. All rights reserved.
//

import UIKit

class PopoverProgVC: UIViewController {
    let dismissButton:UIButton! = UIButton(type:.Custom) 
    let myImage:UIImage! = UIImage(named:"popover_sm")

    func pizzaDidFinish(){
        dismissViewControllerAnimated(true,
        completion: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

// Build a programmatic view
//background
        view.backgroundColor = UIColor(
            red: 0.8,
            green: 0.5,
            blue: 0.2, 
            alpha: 1.0)
//image
       if (myImage != nil){ 
           let myImageView = UIImageView(image: myImage)
           myImageView.frame = view.frame
           myImageView.frame = CGRectMake(10, 10, 200, 200)
           view.addSubview(myImageView)
       }else{
          print("image not found")
}
// add the label
       let myLabel = UILabel()
       myLabel.text = "Cheddar Popover"
       myLabel.frame = CGRectMake(10, 250, 300, 50)
       myLabel.font = UIFont(name: "Helvetica",
           size: 24)
       myLabel.textAlignment = .Left
view.addSubview(myLabel)
// add the done button
        dismissButton.setTitle("Done", 
            forState: .Normal)
        dismissButton.titleLabel.font = UIFont(
            name: "Helvetica", 
            size: 24)
        dismissButton.titleLabel.textAlignment = .Left
        dismissButton.frame = CGRectMake(150,175,200,50)
        dismissButton.addTarget(self,
             action: "pizzaDidFinish",
             forControlEvents: .TouchUpInside)
        view.addSubview(dismissButton)
    }
}

21 thoughts on “The Swift Swift Tutorials: Adding Modal Views and Popovers”

  1. Hmm I’ve made some changes in the code, as Xcode was alerting me, its building without errors, but popover and modal presentation that was set up programmatically is not working. What could be the solution, for this? Thank you

      1. Glad to hear it! Have fun with these things. Hopefully in early November there will be a few more lessons on popovers. The e- book Volume 2 will include more about uses for modals with activity and action Sheets in case you are interested. I’m hoping to make My Novemeber 1st launch date for that.

    1. If you mean two popover controllers open at once, no. there were ways of coding it, but it will get rejected in the AppStore. Apple strongly discouraged it until iOs8, when it became an error.

    1. I was in hurry to answer you so let me give you a little more of the reason Apple, particularly in iOS8 and forward, is so against two popups: size classes. Size classes allow developers to make one storyboard for all devices (except the watch), and do it only once. At least right know there are two size classes which can be used to automatically change the way your presented view controller will show up. In the case of a popover, it will show as a popover on a regular width device, like an ipad. On a compact width device like a iPhone, it shows up as a modal view, taking up all the device screen real estate. Because of this, there is no way to show two modal views at the same time. I go into size classes deeply in both of my books,which you will find more information here

  2. HI,

    If any can help on this

    1. Just i want to launch default app in modal dialog on (On received push notification ) from didReceivedRemoteNotification. is it possible? if yes how can i achieve this.

    Thanks in advance.

    Sanjay

    1. Last time I checked, that breaks about three rules in Apple’s approval process, and a lot of style issues. Use a notification only in that case. Any thing else is asking for a rejection.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s