Make App Pie

Training for Developers and Artists

The Swift Swift Tutorial: How to Use UITableView in Swift

[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:

2015-09-28_08-26-18

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 buttonalignMenuButton. Select that button, and click Center Vertically in Containeran Center Horizontally in Container. Select the Update new Constraints button like this:

2015-09-28_08-33-20

This should give you a screen similar to this:

2015-09-28_10-15-38

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.

2015-09-28_08-37-35

Click on the prices label. Select the pin menu pinMenuButtonfor autolayout. Pin the label 20 points top , 0 points left and 0 points right. Select Items of New constraints, then Add 3 constraints.

2015-09-28_08-39-56

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.

2015-09-28_08-42-16

Add the four constraints. Change the text color to white and the background to red. When done, your storyboard should look like this:

2015-09-28_08-43-48

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.

2015-09-28_09-08-06

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.

2015-09-28_09-24-28

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.

2015-09-28_09-43-07

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:

2015-09-28_11-02-00

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:

2015-09-28_09-49-56

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.

2015-09-28_09-51-31

now go to the table view and you will see the 9.99 price per unit.

2015-09-28_11-02-00

Select the table entry and we get the edit screen.

2015-09-28_09-53-08

Press then hold down the on the stepper to bring the price down to something reasonable, such as 0.07:

2015-09-28_09-55-05

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.

2015-09-28_10-00-43

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.

2015-09-28_10-02-42

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()
    }
}

35 responses to “The Swift Swift Tutorial: How to Use UITableView in Swift”

  1. […] the template to make a split view for PizzaDemo. We’ll cut and paste a lot of code from our last post on table views, and add a few new parts for the Split controller. As an iPad application, we will change our UI a […]

  2. How would I go about making a table view that has two table views, the first would be for a name and description and the second would be independent to that name and description and would hold like cards and definitions for a flashcard type app. I have the first table programmed so that if will accept a name and description but i don’t know how i would make the cell selectable so that it would take it to a cell specific view that holds that specific data. I have a feeling that im going to have to implement core data. Hope for a reply with any ideas. Thanks!

    1. Let me make sure I understand what you are asking:
      1) You want to make an application with two tableviews.
      2) Tableview 1 (name description) and tabeview 2(card, definition) do not talk to each other
      3) You want to select a cell in table 1 and go to a view with the data for table 1.
      4) You are thinking this will require core data

      I’m working a little blind here since I do not completely know the purpose of your app. For setting up two tableviews, make two view controllers each with a tableview and link them together by a segue. Set them up exactly as discussed above. If you are thinking of putting two table views on one view, You’d have to give me (and Apple) a really good reason why two unrelated tableviews need to be on screen at the same time — that would be going against the Human interface guidelines and would be very difficult for a user to make sense of.

      As for selecting a cell and going to a view, you would do as I described above – use a segue. You can use a segue from the selected cell, and if you need a second selection from the cell for detail, you can make a second segue with the detail disclosure and select show detail when you click from the detail to the new view controller.

      Core data is more an issue of persistence, how you save your data between uses of the app. It has nothing to do with the above and there are several choices for dealing with that, including archiving and keeping your own data file in CSV or XML format (or your own creation) you read and write to the device. How one handles persistence in Swift is a big question I’ll be discussing in a future post.

      1. Ok but basically my app has flash cards in it, so the deck that you create is going to have specific cards inside that deck, my question is will segueing keep these card’s specific to the deck. And I believe that I need to use core data because I need to save the card data so that you don’t loose it when clearing the app. If you can understand that it would help a lot!

      2. The answer depends on how you set up your model, not your view controller, so the short answer is no, the segue itself wont do anything to give you selected cards. And that brings two parts. You are correct that core data is one way for saving your data (what I called persistence). It is not the only way, and depending on the data you are moving around, how much there is and how you are using and handling it, there may be better answers, whihc as I said I’ll get into sometime in the future.
        Taking the model-view-controller pattern into account the only answer to your question i can give you is a bit vague. It depends on your data structures in your model or models, the segue itself has nothing at all to do with the data — it’s why prepareforsegue is so important since we need to tell where we are going something about what we did here. Basically you will need two lists — probably arrays since those work best with UITableview. the first is all of the flashcards. the second is all of deck names. You will need to associate a deck name to a flash card, either by a numerical identifier or by the name of the deck. Then you need to make a sub array which has only the identifier cards by finding them. I’d go through the NSArray documentation carefully and also learn about predicates before you do this. If you expect a very small set of total cards, you skip predicates and make the sub-array with your own loop, but performance will suffer with big numbers of flash cards. You’ll need predicates also if you go the core data route, but even core data will have to be set up the same way.

        Sorry I don’t have a simple answer, but you got a lot of work and homework to do on your model if you want this to work.

      3. Ok that’s fine, I’m excited for any challenges, should I read the xcode 6 manual to find more info about NSarrays or what do you recommend? Hope I didn’t bother you with these questions, I’m an avide 15 yr old that is eager to learn a lot about programming in general, thanks again for all your help! Hopefully these questions weren’t to pesky.

      4. I don’t know how much knowledge of array you have. yes the NSArray refrence is very useful, but you have to know a lot about arrays first. find a good introductory book that covers arrays structure, and classes. unfortunately while it gives context, the Swift programming guide does not explain these structures well. Also read Apple’s Human Interface Guidelines manual. it will help with some of the design elements. if anyone ever lets me in front of a classroom it would be the first mandatory assignment.

        good luck on your journey.

      5. Ok thanks, I’ve had some books that were for objective c but I struggled to find any books for swift because of the newness of the language, if you know of any goods ones please let me know, however there may have been some updates in that field with some literature I can use. I will hopefully power through these books in a couple of days and see where I’m at from there. Thanks again for all your information and helpfulness! I will definately stay tuned to any more tutorials that you have in the future!

    2. Ok so im trying to make an array that looks similiar to this:

      var deckNameArray = [Deck:String, Desc:String]

      The deck and description are bother outlets and are variables from the 2 input text fields. What I’m trying to do is make the array values equal to what ever the inputs of the two text fields are. I also tried Deck.text so it would take the text from the text field but still no luck. Is there something totally wrong or is it a small thing? Thanks

      1. You got a lot that needs work there. It’s a multiple step process, which requires you to think in MVC, and a bit of a different data structure than you got there. I have a sneaky suspicion we need to teach you what both classes and collection types are for and how to use them. I’d suggest you read the post I hope to be posting this weekend that I’m working on now. I may try to work something like this into the examples (though it will be of course pizza related) so you see how to do it.

        In the meantime, I’ll give you a style hint, try not to capitalize parameters, variables, constants or other identifiers. Capitalize classes, and types. It’s a convetion that’s how we tell one from the other.

  3. If you can get through the fundamentals of arrays in any language, I’ll be having a Swift version of arrays and collection types (whihc is a lot less difficult than objective-c) sometime in the near future. Check Apple’s Swift Programming language Ebook you can get on iBooks for specific information on Array syntax as well. What you are interested in is how arrays and other data structures work( you might want to look at sets and dictionaries too for the pool of flashcards) , and how you use them in the introductory books.

    Oh and another hint: While not the same, Swift is incredibly similar to Python in its use of Arrays (which are called lists in Python) and Dictionaries. You might want to play around a bit with Python as well to get your feet wet with data structures.

    1. Hey Steven,
      What’s your thoughts on tuples? I think they are more of what I am looking for in my app because of how easy there are to access and use, they seem very flexible and I can name what I want the data to be. I was also looking at arrays but they seem very limited and don’t seem to have as many methods that you can use with the variables that you use when creating them. However with tuples you can have methods and other neat features with the variables that you create. Let me know if there are any downfalls with using tuples with uitableviews because that’s the only thing I’m not sure about how they would work with them. Thanks

      1. Tuples? No. Bad idea. They have no persistence.

      2. Ok so just stick with arrays, so I’m going to need two arrays 1 per view controller that has a uitableview. And is there anyway to implement this into my uitableview that already works with a name and desc so I don’t have to re write and connect the table view all over again? If you want I can show you my code I have already.

      3. Yes, you will have separate models for each view controller, each one probably an array, but not necessarily. If you have only one section in your table view, try coordinating your row index to your array index so it becomes easy to load the data from the array in cellForRowAtIndexPath. What I think you were trying to do with tuples, I think you might want to look more carefully at classes to do.

      4. Ok I will see what I can do and try and research on classes to. Thanks!

      5. Ok, I was also thinking I could link the array into my task manager. Not sure what or how I could but I’ll look into it tonight because I already have a task manager to load the deck name and desc into the table so if I could just use those in the array I would be golden, maybe?

      6. I’m getting the feeling you are way off track and still do not understand arrays. I’m going to give you an assignment, one every Computer Science Student has had since the invention of FORTRAN. Look up the algorithm for a bubble sort. In Swift Take this array

        var sortThisArray = [254, 114, 19, 100, 34, 512, 96, 42, 99]

        print it to the console using the print statement. then sort it in acending order using a bubble sort you write yourself in swift. Do not use any already supplied sorts in the array or NSArray class.

        Once sorted, print it again.

        post your code here. If you can do that, you understand the basics of arrays and I’ll give anothere assignment. until you can do that, you are wasting both of our time.

  4. Ok I managed to figure something out that works. It’s a combination of tutorials that partial worked and then my added code to fix it to make it work. The code that I made in Playground is :

    import Cocoa

    //Numbers that need to be sorted

    var sortTheArray = [254, 114, 19, 100, 34, 512, 96, 42, 99]

    println(“This is the unsorted numbers \(sortTheArray)”)

    //Function to swap numbers

    func swapNumbers (index1 :Int , index2 :Int){

    let numSort = sortTheArray[index1]

    sortTheArray[index1] = sortTheArray[index2]

    sortTheArray[index2] = numSort

    }

    //Numbers that are actually being sorted

    for var firstNum:Int = sortTheArray.count – 1 ; firstNum > 1 ; -firstNum {

    for var jIndex:Int = 0 ; jIndex sortTheArray[jIndex + 1] {

    swapNumbers(jIndex, jIndex + 1)

    println(sortTheArray)
    }
    }
    }

    Now the only downfall that I couldn’t figure out was when I print the finished code to the console it displays it however many times it takes to sort the data. So if it takes 21 times to sort the data it will print out the code 21 times and each time it will show you how it sorted the data with the swapNumbers function, which is kind of cool to be honest. This took a little while longer than I wanted it to because of work and summer school projects. Thanks

    1. Looking good!! I have no deadlines here. I look for quality in people’s work, not speed.

      I got syntax errors when i tried to copy and paste this code into Xcode. I’m betting there is a few typos particularly in the for loops. Also try to simplify your expressions in your loops by taking the calculation outside the loop — it makes for more easily readable code. BY that i mean this:

      var myArray = [1,3,5,6,7,8]
      let start = myArray.count – 1 //this make for easier code in the for loop
      for index = count ; index < 1 ; index– {
      println (myArray[index])
      }

      i left this the same loop you did (hint, hint). Interesting you found one of the optimized methods for bubble sort. The simple one in wikipedia would have sufficed(though it would have given you a lot more than 21 prints) . Great that you got this it is definitely a bubble sort. See if you can fix those errors for me then we can try something really interesting.

      p.s. move you print statement down a few line between closing braces. see what happens.

      1. Here is a link to the download file because I couldn’t manage to fix the typos, Xcode was freaking out on mean when I copied and pasted it as well. Heres the link: https://www.dropbox.com/s/ulom6cjrhjm95ai/Bubble%20Sort.playground

        Also I implemented the simplified code right below the array like you have and i had to modify it a little bit because I was getting syntax errors and came up with this.

        let start = myArray.count;–1 //this make for easier code in the for loop
        var index = count ; index < 1 ; index;–{
        println (myArray[index])
        }

        However it didn't seem to do anything. I left my loop the same but i didn't seem to do anything. Will keep playing with it and also when I placed the println statement lower in the closing braces I get unlimited print outs of the simplified code lol.

      2. change the loop to this. I made few mistakes typing myself. I’m doing this off the top of my head so I can’t proof it Xcode.
        myArray =[1,2,3,5,7,11,13,17]. //array to use
        let start:NSUInteger = myArray.count – 1 //assign a constant start point that is an unsigned integer
        for index = start; index >= 0; index -= 1{ // loop through the end to the beginning of the loop
        println(myArray[index]) // print results
        }

        how would do the same for starting at the beginning of the array and going to the end?

      3. One question, to switch the start points of the array list is it just switching existing code or do I have to write new code? A little lost because I tried a lot of different things.

      4. I’m finishing a blog post on arrays, hopefully by tomorrow. I think much of your questions will be answered when you read it. When first working with arrays. it is often a good idea to get a deck of cards and play computer. Trace every statement like you are the computer and use ten randomly selected cards laid out face up in from of you as the array you are sorting. If you play this game, You probably will be able to answer your own question then and understand what is going on. .

  5. Also let me know if you can open that download because I was having trouble getting it to open.

  6. […] This would put each of the strings in a separate row of the table view. For a discussion on UITableViews, check out The Swift Swift Tutorial: How to Use UITableView in Swift […]

  7. […] my  UITableview Post I used all of this to make a table view of pizzas and price per square inch. In this case, I used […]

  8. Hello,

    Again many thanks for your very educative tutorials.

    I have a question concerning segues:

    “Control-drag from the Pizza Demo Scene’s Type bar button to the table view to make a segue. Select push in the popup”

    When I try to do that, it is said : “Non adaptive action segues – push deprecated”.

    When I try to connect the two controllers with a modal, Nav is not appearing. Action Segue – Show, is it ok?

    Thanks.

    1. Yes, use Show instead. That is the new Push.

      1. Ok. Thanks.
        I have a question about the difference on using didSelectRowAtIndexPath, and prepareForSegue in Table View Controller. I have a section. I know how to get the section index with didSelectRowAtIndexPath : indexPath.section. I could not find the way to get the section index with prepareForSegue. Is there a way to get the section selected with prepareForSegue?
        Thanks a lot/

  9. Hi Steven,
    I just found out how to get index section in prepareForSegue:
    let index = tableView.indexPathForSelectedRow()!
    -> index.section

    1. You’re getting faster than me at answering these questions. Good Job!!

  10. […] an early post on this site, I wrote a table view tutorial. I decided to update that tutorial a bit for The Swift Swift View Controllers Book and add a bit […]

  11. […] The Swift Swift Tutorial: How to Use UITableView in Swift […]

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.