[Converted to to Swift 2.0/iOS9 9/28/15 SJL]
The workhorse control in iOS is the table view. With both a delegate and data source it is powerful, flexible and can get a bit complicated. In this lesson, we will add a table view controller between the Pizza Demo and the Edit Price scenes in our earlier pizza demo app to easily change prices for pizzas. Our goals is to make a simple table, but communicate from the table from another view controller and to another view controller like this:
We’ll start from scratch, so if you did not do those earlier lessons, you will not be lost.
Create the Project
In Xcode, Start a new single-view project called SwiftPizzaTableDemo. Use Swift as the project and Universal as the device. For good programming practices, we’ll be adding some autolayout to this app. Save the application.
Add a Launch Screen
One good practice is to add something to the launchScreen.storyboard. Select the launchScreen.storyboard in the file inspector. Change the background to a color. I used #FFFFEE in the RGB palette. Drag a label to the center of the scene. Change the text to SwiftPizzaTableDemo. For those unfamiliar with launchScreen.storyboard, it has officially replaced the former launch image that displays while your app loads. This creates a universal launch screen that will work correctly on any device. At the bottom of the storyboard you will find the align menu button. Select that button, and click Center Vertically in Containeran Center Horizontally in Container. Select the Update new Constraints button like this:
This should give you a screen similar to this:
Create The Model
When working with table views, making a good model is critical. We’ll use a version of the pizza model used in other lessons for our model. Press Command-N. Make a Cocoa Touch Class subclassing NSObject
called Pizza. When the class appears in Xcode, change the class to this
class Pizza { var pizzaPricePerInSq = ["Cheese": 0.03 , "Sausage": 0.06 , "Pepperoni": 0.05 , "Veggie": 0.04] let pi = 3.1415926 var diameter = 0.0 var pizzaType = "Cheese" }
We have a class with four properties. We’ll be using the dictionary property for our table. This app computes the price of the pizza based on the area of the pizza. We need a few methods to compute that. Add the following code:
func pizzaArea() -> Double{ return radius * radius * pi } func unitPrice() -> Double{ if let unitPrice = pizzaPricePerInSq[pizzaType]{ return unitPrice } else { return 0.0 } } func pizzaPrice() -> Double{ return pizzaArea() * unitPrice() } }
Computed Properties
You’ll notice we get an error that radius
cannot be found. Add the following just below the pizzaType
declaration:
var radius : Double { get{ return diameter/2.0 } }
This is a computed property. Computed properties are a cross between a function and a variable. Like a function they return a computed value. Like a variable they have no parameters, and the value exists every time we reference the variable, like we did in return radius * radius * pi
. While humans describe pizza size by the diameter, we need the radius to compute the area. Computed properties allow us to have the radius handy for our area calculations.
We’ll need one more computed property for our model. Add this below the code for radius
var typeList:[String] { get{ return Array(pizzaPricePerInSq.keys) } }
This creates a string array of the dictionary keys. We’ll see later that this is critical in setting up our table properly.
Make the Starting View Controller
We have our model, and now we can start working on our pizza price calculator. Let’s start with the code, then the storyboard.
The View Controller PizzaDemoVC
Press Command-N and make a new Cocoa Touch class subclassing UIViewController
, named PizzaDemoVC. Change the class to this:
class PizzaDemoVC: UIViewController{ var pizza=Pizza() let clearString = "I Like Pizza!"; @IBOutlet var priceLabel : UILabel! @IBOutlet var resultsDisplayLabel : UILabel! @IBOutlet var pizzaType: UISegmentedControl! @IBAction func pizzaType(sender : UISegmentedControl) { } @IBAction func clearDisplayButton(sender : UIButton) { } @IBAction func sizeButton(sender : UIButton) { } }
We have an instance of our model as a property and three outlets: two for labels, and one for a segmented control. We also have three actions. Flesh out the actions to be this:
@IBAction func pizzaType(sender : UISegmentedControl) { let index = sender.selectedSegmentIndex pizza.pizzaType = sender.titleForSegmentAtIndex(index)! displayPizza() } @IBAction func clearDisplayButton(sender : UIButton) { resultsDisplayLabel.text = clearString pizza.diameter = 0 displayPizza() } @IBAction func sizeSegment(sender : UISegmentedControl) { let index = sender.selectedSegmentIndex let aString = sender.titleForSegmentAtIndex(index)! switch aString { case "Personal": pizza.diameter = 8.0 case "Small": pizza.diameter = 10.0 case "Medium": pizza.diameter = 16.0 case "Large": pizza.diameter = 18.0 default: pizza.diameter = 0.0 } displayPizza() }
Our first method selects our pizza type. Our second clears our current pizza. The third sets our diameter. Since we have text titles and not numbers, we use a switch
to parse the selection into numbers.
We need the displayPizza
method to output our pizza. Add this to your code:
func displayPizza(){ let displayString = String( format:"%6.1fin %@ Pizza", pizza.diameter, pizza.pizzaType) let priceString = String( format:"%6.2f sq in at $%6.2f is $%6.2f", pizza.pizzaArea(), pizza.unitPrice(), pizza.pizzaPrice()) resultsDisplayLabel.text = displayString priceLabel.text = priceString }
We need to do a little initialization as well. Add a viewDidLoad
to this:
override func viewDidLoad() { super.viewDidLoad() resultsDisplayLabel.text = clearString pizza.diameter = 8.0 pizza.pizzaType = "Cheese" displayPizza() view.backgroundColor = UIColor(red:0.99,green:0.9,blue:0.9,alpha:1.0) }
Design the Storyboard
We have a model and view controller. Let’s put together our view. Go to the storyboard. we’ll be using a navigation controller for this storyboard. Click the view controller icon on the visible scene. In the drop down menu, select Editor>Embed in>Navigation Controller to embed our view controller in a navigation controller. On the navigation bar at the top of the scene, double click and change the title of the scene to Pizza. Drag a bar button item to the right side of the navigation bar. Title the button Prices.
Drag two labels out on the scene. Make the text of one label Prices and the other Pizza Description. Drag two segmented controls out on the storyboard. Change the segments to 4 on both of them. Make the titles in one Cheese, Pepperoni, Sausage and Veggie. Title the the second one Personal,Small,Medium,Large. Add a button titled Clear. Arrange the controls so they look like this.
Click on the prices label. Select the pin menu for autolayout. Pin the label 20 points top , 0 points left and 0 points right. Select Items of New constraints, then Add 3 constraints.
Select the second label. Use the same constraints on the label using the pin menu. Select the first segmented control, apply the same constraints, and then add the same constraints for the last segmented control.
For the button, Select 0 points left, 0 points right, and 0 down. Set the height to 50 points and again update constraints.
Add the four constraints. Change the text color to white and the background to red. When done, your storyboard should look like this:
Click on the view controller icon. In the identity inspector, change the class to SwiftPizzaVC. Close the right and left panes to give yourself room. Open the assistant editor in Automatic mode. The left pane should have the storyboard, and the right pane the SwiftPizzaVC
view controller code. From the circles at the left of the outlets and actions drag to the appropriate control.
You have set up the base app. Build and run.
We are now able to get pizza prices.
Add the Table View to the Story Board
After all that we can get to placing the table in. Start by dragging a table view controller onto the storyboard.
Click on the grayed area of the table view and in the properties inspector, make this Dynamic Prototype with one Prototype Cell. Go back to the properties inspector, click on the white table cell, select a right detail style and set the identifier to cell.
Control-drag from the Pizza Demo Scene’s Prices bar button to the table view to make a segue. Select show in the popup. In the properties inspector change the Storyboard Segue Identifier to toTable. In the table view scene add a navigation item to the top of the scene and change the title to Pizza Prices. Then drag a bar button item to the right side of the tool bar. Change the title of the button to Save.
Make and Connect the UITableviewController
On the drop-down menu, select File>New>File… and select Cocoa Touch Class. Name the class PizzaTypeTableVC and make it a subclass of UIViewController
and the language as Swift. While we can make the subclass UITableViewController
, it adds a lot of template code we are not going to use. I tend to start with a View controller. Change
class PizzaTypeTableVC: UIViewController {
to this
class PizzaTypeTableVC: UITableViewController {
Remove everything but viewDidLoad
.
Go to the storyboard. Click on a blank part of the storyboard, then click on the Table View Controller – Pizza Type heading or the view controller icon if the heading is not visible. In the identity inspector, set the custom class to PizzaTypeTableVC. Open the Assistant editor, navigate to PizzaTypeTable.swift if necessary and control_drag the Save bar button to just above the viewDidLoad()
method. Make a action called savePrices.
Just above savePrices()
add a variable to the PizzaTypeTableVC
class so we have a model for this view controller.
var pizza = Pizza()
We are going to transfer the model between the first and second view controller via segues and delegates, this will simplify a lot of what we will do later.
The Three Required Data Source Methods
Table view controllers rely on on a data source and delegate to operate correctly. In Swift these are wrapped into the View controller so they do not need to be adopted by the developer. However, there are three data source methods that developers have to implement for a table view to work. The rest is the icing on the cake.
The first of these is numberOfSectionsInTableView
so it returns one section. As we’ll explain in some of the more advanced table view lessons, there can be more than one section in a table view. For this lesson we are sticking with one. Add the following code
// MARK: Data Sources and Delegates override func numberOfSectionsInTableView(tableView: UITableView?) -> Int { // Return the number of sections. return 1 }
Our second data source methods returns the number of rows. We are using the pizzaPricePerInSq
dictionary for our table and we can get the rows from its count property. Add this code:
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int { return pizza.pizzaPricePerInSq.count }
Our last of the three methods, tableView(tableView:, cellForRowAtIndexPath: )
populates our cells. Add the following method to our class.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier( "cell", forIndexPath: indexPath) //make the cell return cell }
This code does nothing — yet. It is the skeleton structure you must start with for populating a cell. We get the cell of a specific location using the dequeueReusableCellWithIdentifier
function as a UITableViewCell
. Note we have a identifier cell
. This identifier must match the identifier we used in the storyboard exactly. If not, there will be a fatal error.
The other parameter in our data source method is indexPath
. An NSIndexPath
contains two values for our purposes: the section and the row. This is the location of the cell on the table. For the dequeueReusableCellWithIdentifier
we need the full location. As we only have a row, we only need the row component. Add this to your code:
let row = indexPath.row //get the array index from the index path
Default cells have two properties we often use: textLabel
and detailTextLabel
. Both of these are of type UILabel?
. I’ll place my Pizza type in the title and the pizza price in the detail. Except…Tables run on arrays, not dictionaries. Tables need ordered collections, and dictionaries have no order. That’s what the typeList computed property is for. It is an array with an index for each of the pizza types. Once we have a type we can use that type to get the price out of the dictionary. add this to the method:
let rowDataKey = pizza.typeList[row] //the dictionary key
We’ve made a constant rowDataKey
that is the dictionary key. As a string we can put that directly into the cell’s text label. Add this to the end of the method:
cell.textLabel!.text = rowDataKey let rowDataValue = pizza.pizzaPricePerInSq[rowDataKey] //the dictionary value cell.detailTextLabel!.text = String(format: "%6.2f",rowDataValue!) return cell
We can get the Double
value of the the price rowDataValue
from the dictionary with rowDataKey
as our key. We then assign the the key, which is our type, and the value, which is our price, to the table cell. Finally, we return our cell.
We have everything in place to test our table view. Since we have an instance of the model, there is a fully initialized dictionary waiting for us. We do not need the segue to move the data over before testing. Build and run.
Tap the prices button and the prices are now visible.
Setting up Segues
These prices however are coming from the initialized instance of Pizza in each view controller. We want to send the data in the pizza price calculator to the table.
Next, set up the segues to send information to new view controllers.
Click over to the PizzaDemoVC.swift file. Add the following code to the class:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) { if segue.identifier == "toTable" { let vc = segue.destinationViewController as! PizzaTypeTableVC vc.pizza = pizza } }
Since we move the entire model, the segue is rather straightforward. We make our vc
destination and send it our current model. For testing, this provides a small problem: our models are identical when loaded. Add the following code to the viewDidLoad()
in PizzaDemoVC.swift
file:
pizza.pizzaPricePerInSq["Pepperoni"] = 9.99 //test data -- remove when done
The number 9.99
acts as a visible marker to know that the segue worked. Build and run. The table should look like this:
The price for a Pepperoni pizza is 9.99
Changing Prices
While seeing our prices is a cool thing, it would be nicer if we could change them. We would like to tap on a table cell and go to a view that allows us to change the price. On the storyboard, drag out another view controller. Control drag from the cell in the table view to the new controller. Select a Show segue, and name the segue toEdit.
On the new controller drag a navigation item out to the navigation bar. Title it Edit Price. Drag a bar button out, and in the attribute inspector, select a Done button. Add two labels, one titled Pizza Type, the other titled 0.00, and a stepper to the view placing each right under the other.
Using the same constraints we used before, starting at the top control, pin 20 points up 0 points left and 0 points right for each control. You should a have a scene looking like this:
Press Command-N and make a new cocoa touch class subclassing UIViewController
named PriceEditVC. Once loaded, go back to the storyboard. Select the new scene’s view controller icon, and in the identity inspector, Set the class to PriceEditVC
. Close any extra panes, and open the assistant editor. Control drag the Pizza Type Label to the code. Create a outlet pizzaTypeLabel. Control drag the 0.0 label to the code. Make an outlet priceLabel.
For the stepper control, control-drag twice to make a outlet and an action, both named priceStepper Control-Drag from the bar button item to the code and add an action doneBarButton
Your code should look like this when done:
class PriceEditVC:UIViewController{ @IBOutlet weak var pizzaTypeLabel: UILabel! @IBOutlet weak var priceLabel: UILabel! @IBOutlet weak var priceStepper: UIStepper! @IBAction func priceStepper(sender: UIStepper) { } @IBAction func doneBarButton(sender: UIBarButtonItem) { } override func viewDidLoad(){ super.viewDidLoad() } } Clean out any code that is not this code. For this example you won't need it or will replace it. Towards the top of the class add two properties: var pizzaType = "Pizza Type" var pizzaPrice = 0.0
We need to configure our stepper. Change viewDidLoad
to this:
override func viewDidLoad{ super.viewDidLoad() priceStepper.value = pizzaPrice priceStepper.stepValue = 0.01 priceStepper.maximumValue = 1.00 priceStepper.minimumValue = 0 displayPrice() }
This code sets the stepper value to the PizzaPrice
value, and sets a range of 0.0 to 1.0 for the stepper, with 0.01 for the step size. We do have an error. We do not have a displayPrice
function. Add the following:
func displayPrice(){ priceLabel.text = String(format:"Price %3.2f per sq unit",pizzaPrice) pizzaTypeLabel.text = pizzaType }
Add the following to the priceStepper
action to update the pizzaPrice
and display
@IBAction func priceStepper(sender: UIStepper) { pizzaPrice = sender.value displayPrice() }
This view controller is dependent on data from the table. We will select a row and want the data from that row to go to EditPriceVC
. Go to the table view controller code. At the bottom of the class, add this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { let index = tableView.indexPathForSelectedRow! pizza.pizzaType = pizza.typeList[index.row] if segue.identifier == "toEdit" { let vc = segue.destinationViewController as! PriceEditVC vc.pizzaType = pizza.pizzaType vc.pizzaPrice = pizza.unitPrice() }
We start by getting an index path using the indexPathforSelectedRow()
property. This property returns the NSIndexPath
of the selected row. We need the row of the index to find a corresponding entry in our list of keys,so in the next line we use index.row
to get the row the entry is on. We are not using the full Pizza
in the destination controller. we therefore set our current model to a type based on the index, and send the data from that. Using our computed propertytypeList
we set pizza.pizzaType
to that key. Now our model has a selection of a pizza. Any segue we do now will reflect that selection. If our segue is to the toEdit segue, we set in the new view controller the pizzaType
and pizzaPrice
properties.
Build and run. Select a medium pepperoni pizza, which is very expensive.
now go to the table view and you will see the 9.99 price per unit.
Select the table entry and we get the edit screen.
Press then hold down the – on the stepper to bring the price down to something reasonable, such as 0.07:
Set up the Delegates
Unfortunately, we cannot save that price. We’ll need some delegates for that. we will need two delegate to get this to show up in our price calculator. The first will take the price to the table. The second delegate will send the corrected model back to the calculator. If you have not worked with delegates I suggest you look at these two delegate posts to help you understand: Using Segues and Delegates in Navigation Controllers and Why Do We Need Delegates?
The Price Edit to Type Table Delegate
In the EditPriceVC.swift file, we first need to set up a protocol and use it in the target action for the Done button. First add the protocol above the class:
protocol PriceEditDelegate{ func priceEditDidFinish(price:Double, type:String, controller:EditPriceVC)
Next, add a delegate to the class
var delegate:PriceEditDelegate! = nil
Add the code to the Done action
@IBAction func doneBarButton(sender: UIBarButtonItem) { delegate!.priceEditDidFinish(pizzaPrice, type: pizzaType,controller:self) }
Go to PizzaTypeTableVC
and adopt the protocol
class PizzaTypeTableVC: UITableViewController, EditPriceDelegate {
We get the classic Xcode complaint that the protocol isn’t implemented. Write the required method for the protocol.
func priceEditDidFinish(controller: PizzaTypePriceVC, type: String, price: Double,controller:EditPriceVC) { pizza.pizzaType = type pizza.pizzaPricePerInSq[pizza.pizzaType] = price controller.navigationController?.popViewControllerAnimated(true) tableView.reloadData() }
The delegate loads the new data into the model, changing the price in the pizzaPricePerInSq
dictionary. Just changing this does not change it in the table automatically. The tableView.reloadData()
refreshes the data for the table so it reflects correct prices.
Assign self
to the delegate in the prepareForSegue()
for the toEdit
segue:
vc.delegate = self
Build and run. Again using the pepperoni pizza, go to the edit price view. Set the price, and hit save. The new price show up on the table.
The Table to Pizza Demo Delegate
We still need to get the price change to the Pizza demo page, so we need another delegate. For this delegate, make a protocol in the PizzaTypeTableVC.swift file to delegate back to the PizzaDemoVC
class. Above the class declaration for PizzaTypeTableVC
add this:
protocol PizzaTypeTableDelegate{ func pizzaTypeTableDidFinish(controller:PizzaTypeTableVC, pizza:Pizza) }
In the PizzaTypeTableVC
class, add the delegate
var delegate:PizzaTypeTableDelegate! = nil
Implement the code in our bar button item target-action:
@IBAction func savePrices(sender: UIBarButtonItem) { delegate!.pizzaTypeTableDidFinish(self, pizza: pizza) }
Next we need to modify PizzaDemoVC
. Adopt the protocol:
class PizzaDemoVC: UIViewController,PizzaTypeTableDelegate {
Xcode, right on cue, starts complaining that the protocol is not implemented. Add the following code:
func pizzaTypeTableDidFinish(controller: PizzaTypeTableVC, pizza: Pizza) { self.pizza = pizza controller.navigationController.popViewControllerAnimated(true) displayPizza()
While we don’t have to use self
in most of our properties, Line 2 is one of the few places where self
is mandatory. We have an ambiguous situation between the property pizza
and the parameter pizza
. By scope rules, Swift will assume pizza
is the parameter. To assign to the property we must use self.pizza
The last step is to add vc.delegate = self
to our prepareForSegue()
:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) { if segue.identifier == "toTable" { let vc = segue.destinationViewController as PizzaTypeTableVC vc.pizza = pizza vc.delegate = self } }
Build and run. Change the price for a pepperoni pizza and change it to 0.07 by pressing the – button. Tap Done. Pepperoni should be 0.07 in the table. Tap Save. Now our pizza has a reasonable price.
There’s Always One More Bug
There is one thing I’d like to add to this. Whatever pizza we change the price of should be the selected pizza in the pizza demo. I may start by selecting cheese, but if I then edit pepperoni, I’d like the selector to read pepperoni. To fix this, change displayPizza
to
func displayPizza(){ let displayString = String( format:"%6.1fin %@ Pizza", pizza.diameter, pizza.pizzaType) let priceString = String( format:"%6.2f sq in at $%6.2f is $%6.2f", pizza.pizzaArea(), pizza.unitPrice(), pizza.pizzaPrice()) resultsDisplayLabel.text = displayString priceLabel.text = priceString //update the segment index to match new pizza type for index in 0..<pizzaType.numberOfSegments{ if pizza.pizzaType == pizzaType.titleForSegmentAtIndex(index){ pizzaType.selectedSegmentIndex = index } } }
I implemented a quick loop through the title in the selected index, and if I find a match, I set it as the selected index. I use the
for index in 0..<pizzaType.numberOfSegments
statement with a range of 0..<pizzaType.numberOfSegments
which I find a lot cleaner than
for var index = 0; i > pizzatype.numberOfSegments; i++
the ..<
range excludes the last value in the range, just what we need for accessing indexes.
Build and run, and the segmented control now behaves itself.
The Whole Code
Pizza.swift
// // Pizza.swift // pizzaDemo // // Created by Steven Lipton on 7/1/14. // Copyright (c) 2014 Steven Lipton. All rights reserved. // Updated to Swift 2.0/iOS9 9/22/15 SJL import UIKit /* -------- Our model for MVC keeps data and calculations about pizzas ------------*/ class Pizza { var pizzaPricePerInSq = ["Cheese": 0.03 , "Sausage": 0.06 , "Pepperoni": 0.05 , "Veggie": 0.04] let pi = 3.1415926 var diameter = 0.0 var pizzaType = "Cheese" var radius : Double { //computed property get{ //must define a getter return diameter/2.0 } } var typeList:[String] { get{ return Array(pizzaPricePerInSq.keys) } } func pizzaArea() -> Double{ return radius * radius * pi } func unitPrice() -> Double{ if let unitPrice = pizzaPricePerInSq[pizzaType]{ return unitPrice } else { return 0.0 } } func pizzaPrice() -> Double{ return pizzaArea() * unitPrice() } }
PizzaDemoVC.swift
// // PizzaDemo.swift // pizzaDemo version 7/12/14 // adds a table view to the application // // Created by Steven Lipton on 6/8/14. // Copyright (c) 2014 Steven Lipton. All rights reserved. // Updated to Swift 2.0/iOS9 9/28/15 SJL // import UIKit class PizzaDemoVC: UIViewController, PizzaTypeTableDelegate { var pizza=Pizza() let clearString = "I Like Pizza!" @IBOutlet var priceLabel : UILabel! //added 07/01/14 @IBOutlet var resultsDisplayLabel : UILabel! @IBOutlet var pizzaType: UISegmentedControl! @IBAction func pizzaType(sender : UISegmentedControl) { let index = sender.selectedSegmentIndex pizza.pizzaType = sender.titleForSegmentAtIndex(index)! displayPizza() } func pizzaTypeTableDidFinish(controller: PizzaTypeTableVC, pizza: Pizza) { self.pizza = pizza controller.navigationController!.popViewControllerAnimated(true) displayPizza() } func displayPizza(){ let displayString = String( format:"%6.1fin %@ Pizza", pizza.diameter, pizza.pizzaType) let priceString = String( format:"%6.2f sq in at $%6.2f is $%6.2f", pizza.pizzaArea(), pizza.unitPrice(), pizza.pizzaPrice()) resultsDisplayLabel.text = displayString priceLabel.text = priceString //update the segment index to match new pizza type for index in 0..<pizzaType.numberOfSegments{ if pizza.pizzaType == pizzaType.titleForSegmentAtIndex(index){ pizzaType.selectedSegmentIndex = index } } } @IBAction func sizeSegement(sender : UISegmentedControl) { let index = sender.selectedSegmentIndex let aString = sender.titleForSegmentAtIndex(index)! switch aString { case "Personal": pizza.diameter = 8.0 case "Small": pizza.diameter = 10.0 case "Medium": pizza.diameter = 16.0 case "Large": pizza.diameter = 18.0 default: pizza.diameter = 0.0 } displayPizza() } @IBAction func clearDisplayButton(sender : UIButton) { resultsDisplayLabel.text = clearString pizza.diameter = 0 displayPizza() } override func viewDidLoad() { super.viewDidLoad() resultsDisplayLabel.text = clearString pizza.diameter = 8.0 pizza.pizzaType = "Cheese" pizza.pizzaPricePerInSq["Pepperoni"] = 9.99 //test data -- remove when done displayPizza() view.backgroundColor = UIColor(red:0.99,green:0.9,blue:0.9,alpha:1.0) } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "toTable" { let vc = segue.destinationViewController as! PizzaTypeTableVC vc.pizza = pizza vc.delegate = self } } }
PizzaTypeTableVC.swift
// // PizzaTypeTableVC.swift // PizzaDemo // // Created by Steven Lipton on 7/11/14. // Copyright (c) 2014 Steven Lipton. All rights reserved. // Update to Swift 2.0/ iOS9 9/28/15 SJL import UIKit protocol PizzaTypeTableDelegate{ func pizzaTypeTableDidFinish(controller:PizzaTypeTableVC, pizza:Pizza) } class PizzaTypeTableVC: UITableViewController, PriceEditDelegate{ var pizza = Pizza() var delegate:PizzaTypeTableDelegate? = nil @IBAction func savePrices(sender: UIBarButtonItem) { delegate?.pizzaTypeTableDidFinish( self, pizza: pizza) } override func viewDidLoad() { super.viewDidLoad() tableView.rowHeight = UITableViewAutomaticDimension } func priceEditDidFinish(price: Double, type: String, controller:PriceEditVC) { pizza.pizzaType = type pizza.pizzaPricePerInSq[pizza.pizzaType] = price controller.navigationController?.popViewControllerAnimated(true) tableView.reloadData() } // #pragma mark - Table view data source override func numberOfSectionsInTableView(tableView: UITableView?) -> Int { return 1 } override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int { // Return the number of rows in the section. return pizza.pizzaPricePerInSq.count } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { //note I did not check for nil values. Something has to be really broken for these to be nil. let row = indexPath.row //get the array index from the index path let cell = tableView.dequeueReusableCellWithIdentifier( "cell", forIndexPath: indexPath) //make the cell let rowDataKey = pizza.typeList[row] //the dictionary key cell.textLabel!.text = rowDataKey let rowDataValue = pizza.pizzaPricePerInSq[rowDataKey] //the dictionary value cell.detailTextLabel!.text = String(format: "%6.2f",rowDataValue!) return cell } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { let index = tableView.indexPathForSelectedRow! //changed to property pizza.pizzaType = pizza.typeList[index.row] if segue.identifier == "toEdit" { let vc = segue.destinationViewController as! PriceEditVC vc.pizzaType = pizza.pizzaType vc.pizzaPrice = pizza.unitPrice() vc.delegate = self } } }
PriceEditVC.swift
// // PriceEditVC.swift // SwiftTablePizzaDemo // // Created by Steven Lipton on 9/28/15. // Copyright © 2015 MakeAppPie.Com. All rights reserved. // import UIKit protocol PriceEditDelegate{ func priceEditDidFinish(price:Double, type:String, controller:PriceEditVC) } class PriceEditVC:UIViewController{ var pizzaType = "Pizza Type" var pizzaPrice = 0.0 var delegate:PriceEditDelegate! = nil @IBOutlet weak var pizzaTypeLabel: UILabel! @IBOutlet weak var priceLabel: UILabel! @IBOutlet weak var priceStepper: UIStepper! @IBAction func priceStepper(sender: UIStepper) { pizzaPrice = sender.value displayPrice() } @IBAction func doneBarButton(sender: UIBarButtonItem) { delegate!.priceEditDidFinish(pizzaPrice, type: pizzaType, controller:self) } func displayPrice(){ priceLabel.text = String(format:"Price %3.2f per sq unit",pizzaPrice) pizzaTypeLabel.text = pizzaType } override func viewDidLoad(){ super.viewDidLoad() priceStepper.value = pizzaPrice priceStepper.stepValue = 0.01 priceStepper.maximumValue = 1.00 priceStepper.minimumValue = 0 displayPrice() } }
Leave a Reply