The Swift Swift Tutorial: Using Delegates and Segues in Swift Part 2 — The Pizza Demo App

In our last post, we went through a very basic framework for segues and delegates in Swift. This time, we will add the segues and delegates to the pizza demo application so we can change the prices in the dictionary for the pizzas.

Swift and Xcode is still in beta, and there are quirks I’ll mention in passing with the phrase it’s a beta thing. I’ve updated to beta 2, and I haven’t found any major changes in what we did from the last post. Update for GMSeed: there are two big problems since I originally posted this, both related to optionals. One is that optionals are not considered boolean values so if myOptional{do someting} must be if (myOptional != nil) {do something}. The second one is Apple is standardizing the API for what is and is not optional. Much of what is optional has gone from implicit to explicit, and some is no longer optional. I’ve made the changes to the code here, but if you find any more, let me know in a comment.

Separate the Model

For simplicity’s sake back in my post about building a MVC model, I kept the model in the same file as the view controller. It’s time to change that. In Xcode go to File>New>File… and select iOS and then Swift file named Pizza.swift. This will open a completely blank Xcode file.

I did a few changes to the model. Copy and paste this code for the model:

/* --------

Our model for MVC
keeps data  and calculations
about pizzas

note: for ease in copying I left this in one file
you can make a separate file and use import instead.

------------*/

class Pizza {
//changed pizzaPricePerInSq from let to var for segues and delegates 7/1/14
    var pizzaPricePerInSq = [
        "Cheese": 0.03 ,
        "Sausage": 0.06 ,
        "Pepperoni": 0.05 ,
        "Veggie": 0.04]
    let pi = 3.1415926

    var pizzaDiameter = 0.0
    let maxPizza = 24.0
    var pizzaType = "Cheese"

    var radius : Double {  //computed property
        get{   //must define a getter
             return pizzaDiameter/2.0
        }
        set(newRadius){ //optionally define a setter
             pizzaDiameter = newRadius * 2.0
        }
}

var area :  Double {
   get{
       return pizzaArea()
   }
}

func pizzaArea() -> Double{
     return radius * radius * pi
}

//added method 7/1/14 for segues and delegates installment
     func unitPrice() -> Double{
     let unitPrice = pizzaPricePerInSq[pizzaType]    //optional type ?Double
     if (unitPrice != nil ){
         return unitPrice!
     }                               //optional type ?Double checking for nil
    else {
        return 0.0
    }
}

    func pizzaPrice() -> Double{
        let unitPrice = pizzaPricePerInSq[pizzaType]    //optional type ?Double
        if (unitPrice != nil){                                   //optional type ?Double checking for nil
            return pizzaArea() * unitPrice!             //unwrapping the optional type
        }
        return 0.0
    }

func diameterFromString(aString:NSString) -> Double {
    switch aString {
        case "Personal":
            return 8.0
        case "10\"":
            return 10.0
        case "12\"":
            return 12.0
        case "16\"","15\"":
            return 16.0
        case "18\"":
            return 18.0
        case "24\"":
            return 24.0
        default:
            return 0.0
     }
}
}

Once copied, comment out or delete the model from the view controller to rid yourself of the duplicate class error.

A Few Changes in the Model

In line 15 above I changed the dictionary from unmutable to mutable by changing from let to var. In lines 44-53 I added one more method we will use shortly to display the unit price of a pizza.

//added method 7/1/14 for segues and delegates installment
func unitPrice() -> Double{
    let unitPrice = pizzaPricePerInSq[pizzaType]    //optional type ?Double
    if (unitPrice != nil){
        return unitPrice!
    }                               //optional type ?Double checking for nil
    else {
        return 0.0
   }
}

The method unwraps the unit price for a pizza from the dictionary and returns it as a double.

Make a Better Display

With the large amount of displayed data in the pizza demo we  can split the output into two lines. Go over to the storyboard and add another blank label towards the top of the view. I also added two title labels for my other controls.

Basic Pizza Demo Layout
Basic Pizza Demo Layout
Wire up the new blank label to the view controller file (which I renamed PizzaDemoVC.swift) as a outlet

@IBOutlet var priceLabel : UILabel!   //added 07/01/14

Change the displayPizza() method to the following:

func displayPizza(){
    let displayString = NSString(format:"%6.1fin %@",pizza.pizzaDiameter, pizza.pizzaType)
    let priceString = NSString(format:"%6.2f sq in at $%6.2f is $%6.2f",pizza.pizzaArea(),pizza.unitPrice(),pizza.pizzaPrice()) //added 6/29/14
    resultsDisplayLabel.text = displayString
    priceLabel.text = priceString //added 6/29/14
}

Here we use the NSString(format:) constructor, the Swift version of Objective-C’s [NSString stringWithFormat:] to format the strings using our data. Make sure the viewDidLoad, clearDisplayButton, sizeButton and pizzaType all call this function as the last statement in their methods. There is a better way to do this with notifications, but we will cover that in a later lesson.

Embed in a Navigation Controller

If we are using segues and delegates, we’ll need a navigation controller. Go over to the storyboard and click on the pizza demos navigation controller button. From the drop-down select Editor>Embed in >Navigation controller. Xcode will make a navigation controller, and allocate space on your view for a navigation controller bar at the top. Move your controls around if you need to space things properly. Then drag a navigation item to the grayed out box at the top of the view. You must do this step or the next will not work. It’s a beta thing. Drag a bar button to the top right corner. In the properties box (do not double-click, it’s a beta thing) change the title of the bar button to Type The layout should look something like this, but you can change it to your taste.

Completed Pizza Demo Page.
Completed Pizza Demo Page.

Make the New Scene and Segue

Still in Storyboard, drag out another view controller. From the Type bar button, control-drag from the bar button to the new view. A popup will ask for the type of segue and pick Show. In the properties inspector, name the segue typeprice.
Now that you connected the two controllers, you can make and connect the view controller file (it’s a beta thing, they have to be in this sequence) in the drop down menu, go to File>New>File… and click the iOS source button. Make sure you have iOS source and not OS X selected(beta thing), then click Cocoa Touch Class. Make a subclass of UIViewController named PizzaTypePriceVC and set the language to Swift. Don’t create a XIB file. Once Xcode creates the file, delete everything in the file except the viewDidLoad() method.

Alternatively, you can make a blank Swift file as described for the model. Name the file PizzaTypePriceVC, and replace the code there with this:

import UIKit

class PizzaTypePriceVC:UIViewController{

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

In both cases the above is what your code should look like this when completed.

Go back into the storyboard. Click on a blank spot on the storyboard to deselect everything. Click on the view controller icon for the new scene, which should highlight blue. If it is hiding, it is under the title at the top of the view. Go to the identity inspector, and in the Class drop down select PizzaTypePriceVC.

Drag two labels, a text field and a stepper in the new scene. Arrange them like this:

Layout for the Pizza Type scene
Layout for the Pizza Type scene
For the stepper, in the properties inspector set the maximum to 10 with a step of 0.01. Check on Autorepeat and Continuous. For the text field, set the keyboard to Decimal Pad. Now add a navigation Item at the top of the screen, and change the label to Pizza Type. Add a bar button item and in the properties (beta– don’t double-click to change) change the title to Done.

Set up the Basic View Controller

Open the assistant editor, then control-drag the appropriate controls to make the following outlets and actions in the PizzaTypeVC.swift file:

@IBOutlet var pizzaTypeLabel: UILabel!
@IBOutlet var priceStepper: UIStepper!
@IBOutlet var priceText: UITextField!
@IBAction func priceStepper(sender: UIStepper) {
}
@IBAction func priceText(sender: UITextField) {
}
@IBAction func doneButton(sender: UIBarButtonItem) {
}

Between the outlets and actions, add the following variables and methods:

var pizzaType = "pizza Type"
var pizzaPrice = 0.0
func displayPizzaPrice(){
//    pizzaPriceLabel.text = NSString(format:"%6.3f",pizzaPrice)
}
func displayPizzaPriceInText(){
     priceText.text = NSString(format:"%6.3f",pizzaPrice)
}

We’ll use pizzaType and pizzaPrice as properties to transfer values in the segue. We’ll use the two methods to update the text field and label with changes to these properties. Now add the code for two of our three target-action methods:

@IBAction func priceText(sender: UITextField) {
    let priceString:NSString = sender.text
    pizzaPrice = priceString.doubleValue
    displayPizzaPrice()
}
@IBAction func priceStepper(sender: UIStepper) {
    pizzaPrice = sender.value
    displayPizzaPrice()
    displayPizzaPriceInText()
}

Change the code for viewDidLoad() to this:

override func viewDidLoad() {
    super.viewDidLoad()
    pizzaTypeLabel.text = pizzaType
    priceStepper.value = pizzaPrice as CDouble
    displayPizzaPrice()
    displayPizzaPriceInText()

Lines 3 and 4 sets the UI to the correct values, and 4 and 5 displays them.

Prepare prepareForSegue()

We can transfer the pizza type and price using the prepareForSegue() method. Add the following to the PizzaDemoVC class:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
     if segue.identifier == "typeprice" {
         let vc = segue.destinationViewController as PizzaTypePriceVC
         vc.pizzaType = pizza.pizzaType
         vc.pizzaPrice = pizza.unitPrice()
     }
}

For the segue with identifier typeprice, we create a view controller instance for the segue’s destination, and cast it to what we know is on the other end: PizzaTypePriceVC.  Once we have a vc, we set vc‘s properties. We set vc as a constant. As a constant, we have better memory allocation, and still have access to vc‘s properties.

This is all that we need for the segue. Try building and running. If it works right, you the price per square inch of pizza  appears in the text box. If it doesn’t, you might find yourself staring at the app delegate code and a run-time error. If your code is 100% correct, there is one more thing you can try. In the storyboard, click the icon for the view controllers. Change the module from its grayed-out default setting to PizzaDemo. For some reason, this clears the problem.

Setting up the Delegate

I always get my segue working first before I code my delegate. If the segue isn’t working, the delegate won’t. Since we now have working code for the prepareForSegue(), we can set up the delegate. Start with the protocol in the PizzaTypePriceVC.swift file. Between class PizzaTypePriceVC: UIViewController {and the import UIKit add the following:

protocol PizzaTypePriceDelegate{
    func pizzaTypeDidFinish(controller:PizzaTypePriceVC, type:String,price:Double)
}

The protocol has three parameters. The controller parameter we’ll use to get access to the entire PizzaTypePriceVC view controller. The parameters type and price are the two pieces of data we need to move back into the original controller. Technically, we could grab them from PizzaTypePriceVC, but I tend not to do that for clarity and documentation. Next we define the delegate. In the PizzaTypePriceVC class, add the following declaration:

var delegate:PizzaTypePriceDelegate? = nil

Our delegate is an optional value of our protocol. We set it to nil initially. We have a delegate to use in PizzaTypePriceVC, thus we can use it in our target action for the Done Bar Button. Add the following code to the doneButton method:

@IBAction func doneButton(sender: UIBarButtonItem) {
    if (delegate != nil) {
        delegate!.pizzaTypeDidFinish(self, type: pizzaType, price: pizzaPrice)
    }
}

Since delegate is an optional, we test for nil before we try to use it. Once we test for nil, we unwrap it, and use the delegate method. We haven’t written the code for the delegate method yet. Change over to the PizzaDemoVC and adopt the protocol. Change the class definition to the following:

class PizzaDemoVC: UIViewController, PizzaTypePriceDelegate {

The order here is significant. The superclass such as UIViewCntroller is always first, followed by delegates like PizzaTypePriceDelegate. We should have Xcode complaining we didn’t implement this right, so now we can write our required method into the class:

func pizzaTypeDidFinish(controller: PizzaTypePriceVC, type: String, price: Double) {
    pizza.pizzaType = type
    pizza.pizzaPricePerInSq[pizza.pizzaType] = price
    controller.navigationController?.popViewControllerAnimated(true)
    displayPizza()
}

The protocol has as parameters the data we are moving back to PizzaDemoVC. In lines 2 and 3 we can assign the data back to its respective places. In Line 4 we dismiss the PizzaTypePriceVC view controller. Finally, we refresh our view.

Finally, we need to assign the delegate. In prepareForsegue() add the following after the other two assignments:

vc.delegate = self

Build and run. If all works well you should be able to change the prices of the pizzas.

There Is Always One More Bug

You might notice that you can change the prices of the pizzas by the + or but the delegate seemingly does not change the price with input from the text field. The reason is not the delegate but the text field. We have the wrong event for the target action. In the storyboard, right-click on the text field in Edit Price and you will see the default event of Editing Did End.
Screenshot 2014-07-11 09.54.52
Since there is no way to end editing on this view, we really want Editing Changed. Click the X next to the Pizza Type Price VC connection to Editing Did End to break the current connection. Bring up the assistant editor, and navigate to the PizzaTypePrice file if you need to. Right click the text field again and drag the circle for Editing Changed to the code for priceText(). Build and run and you should be able to change the price by the text field now.

Changing the price also needs to update the stepper’s value, so change priceText() to:

@IBAction func priceText(sender: UITextField) {
     let priceString:NSString = sender.text
     pizzaPrice = priceString.doubleValue
     displayPizzaPrice()
     priceStepper.value = pizzaPrice
}

One other change is far more cosmetic. Add this to the viewDidLoad() for PizzaTypePriceVC

    priceText.becomeFirstResponder()

This will bring up the keyboard on load, and let you edit the price immediately with the keyboard.

The Whole Code

Here is the code for both of the view controllers for your reference. The model you can find above.

//
//  ViewController.swift
//  pizzaDemo version 2
//  adds a model class to demonstrate class
//
//  Created by Steven Lipton on 6/8/14.
//  Copyright (c) 2014 Steven Lipton. All rights reserved.
//
//

import UIKit

/*----------

The View Controller

-----------------*/
class ViewController: UIViewController,PizzaTypePriceDelegate {

let pizza = Pizza()
let clearString = "I Like Pizza!"

@IBOutlet var priceLabel : UILabel!   //added 06/29/14
@IBOutlet var resultsDisplayLabel : UILabel!

@IBAction func pizzaType(sender : UISegmentedControl) {
let index = sender.selectedSegmentIndex
pizza.pizzaType = sender.titleForSegmentAtIndex(index)!
displayPizza()
}

func displayPizza(){
let displayString = NSString(format:"%6.1fin %@",pizza.pizzaDiameter, pizza.pizzaType)
let priceString = NSString(format:"%6.2f sq in at $%6.2f is $%6.2f",pizza.pizzaArea(),pizza.unitPrice(),pizza.pizzaPrice()) //added 6/29/14
resultsDisplayLabel.text = displayString
priceLabel.text = priceString //added 6/29/14
}

@IBAction func sizeButton(sender : UIButton) {
pizza.pizzaDiameter = pizza.diameterFromString(sender.titleLabel!.text!)
displayPizza()
}

@IBAction func clearDisplayButton(sender : UIButton) {
resultsDisplayLabel.text = clearString
pizza.pizzaDiameter = 0
displayPizza()
}

override func viewDidLoad() {
super.viewDidLoad()
resultsDisplayLabel.text = clearString
displayPizza()
view.backgroundColor = UIColor(red:0.99,green:0.9,blue:0.9,alpha:1.0)
}
func pizzaTypeDidFinish(controller: pizzaTypePriceVC, type: String, price: Double) {
pizza.pizzaType = type
pizza.pizzaPricePerInSq[pizza.pizzaType] = price
controller.navigationController?.popViewControllerAnimated(true)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "typeprice" {
let vc = segue.destinationViewController as pizzaTypePriceVC
vc.pizzaType = pizza.pizzaType
vc.pizzaPrice = pizza.unitPrice()
vc.delegate = self
}
}

}

Here is the the PizzaTypePriceVC.swift file

//
//  pizzaTypePriceVC.swift
//  pizzaDemo
//
//  Created by Steven Lipton on 7/1/14.
//  Copyright (c) 2014 Steven Lipton. All rights reserved.
//

import UIKit
protocol PizzaTypePriceDelegate{
func pizzaTypeDidFinish(controller:PizzaTypePriceVC, type:String,price:Double)
}

class PizzaTypePriceVC: UIViewController {
@IBOutlet var pizzaTypeLabel: UILabel!
@IBOutlet var pizzaPriceLabel: UILabel!
@IBOutlet var priceStepper: UIStepper!
@IBOutlet var priceText: UITextField!

var pizzaType = "pizza Type"
var pizzaPrice = 0.0
var delegate:PizzaTypePriceDelegate? = nil
func displayPizzaPrice(){
//    pizzaPriceLabel.text = NSString(format:"%6.3f",pizzaPrice)
}
func displayPizzaPriceInText(){
priceText.text = NSString(format:"%6.3f",pizzaPrice)
}
@IBAction func priceText(sender: UITextField) {
let priceString:NSString = sender.text
pizzaPrice = priceString.doubleValue
displayPizzaPrice()
priceStepper.value = pizzaPrice
}
@IBAction func priceStepper(sender: UIStepper) {
pizzaPrice = sender.value
displayPizzaPrice()
displayPizzaPriceInText()
}

@IBAction func doneButton(sender: UIBarButtonItem) {
if delegate {
delegate!.pizzaTypeDidFinish(self, type: pizzaType, price: pizzaPrice)
}
}

override func viewDidLoad() {
super.viewDidLoad()
pizzaTypeLabel.text = pizzaType
priceStepper.value = pizzaPrice as CDouble
priceStepper.stepValue = 0.01
priceStepper.maximumValue = 10.00
priceStepper.minimumValue = 0
priceText.becomeFirstResponder()
displayPizzaPrice()
displayPizzaPriceInText()
}
}

10 Replies to “The Swift Swift Tutorial: Using Delegates and Segues in Swift Part 2 — The Pizza Demo App”

  1. Hello,
    You tutorials are very helpful. Thanks a lot.
    I was wondering if in iOS 8, there is a way to implement Carousel?
    Carousel a widely used now,
    Thanks,
    Gil

  2. Awesome. Thanks.
    Question though… what if the second view controller in embedded in it’s own UINavigationController? (this happens in an app I’m working on).
    When I use prepareForSegue I can either segue to the second view controller, but NOT embedded in the UINavigationController as I’d like; or I can segue to the UINavigationController, but then I can’t set the delegate property. Frustrating! Any help would be appreciated.

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