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.
@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.

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:
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.
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() } }
Leave a Reply