Custom Table View Cells in Swift 3

2016-10-14_06-53-47In iOS you are limited to two labels and an image for one of the standard table view cell styles. You might want more custom cells than this. You are not alone with  other developers including, Instagram, Twitter, Feedly and even Apple often using more than the basic table view cell. You can customize table view cells and populate them with any number of views in any size. In this lesson, I’ll show you how to make a custom table view cell to display four labels. We’ll add some logic to the custom view to conditionally show two labels for specials and give a 10% discount.

I’ll modify the app I created in the Table Views in Subviews Lesson. You can go to that lesson to learn more or download the starter file pizzatable_customcell_start.zip.

Creating a Custom Cell

Go to the storyboard. In the document outline, select the cell

2016-10-14_07-13-44

In the attributes inspector, Change the style to Custom.

2016-10-14_07-16-24

The  height of the custom cell will be bigger than the standard 44 point height. Click the ruler to change from the attributes inspector to the size inspector.  You’ll find the row height at the top of the inspector. Click on Custom and change from the default 44 points to 75 points.

2016-10-14_07-20-29

Click back to the attributes inspector. Drag four labels into the table view cell. Change the labels to Menu Item DescRegular Price $99.99, Special, and $99.99

 2016-10-14_07-24-48

Drag the labels to the four corners of the cell. Use the blue guides to align them to the corners.

2016-10-14_07-28-28

Select the Menu Item Desc Label. In the attributes inspector, change the font to a custom font of Georgia 24 point. Set AutoShrink to Minimum Font Scale of 0.5. In case the description is too long, this will shrink the font to fit.

2016-10-14_07-30-46

Select Special. Change the font color to Red(#ff0000) and the Font to System Heavy 17.0. Right align the label. Select the $99.99   label. right align the label and set the font to Gerogia 22 point.

2016-10-14_07-37-36

I’ll use  auto layout to lay things out cleanly.  Select Menu Item Desc. Control-drag up and to the left until the content view highlights.

2016-10-14_07-38-58

Release the mouse button. A menu appears.  Shift select Leading Space to Container Margin, Top Space to Container Margin and Equal Widths.

2016-10-14_07-40-55

This pins the label to the upper right side, and sets the width of the label to the width of the cell. I want 70% of the cell to be the label. With the label selected, Click on the width constraint, which should have some number like -198 in it

2016-10-14_07-45-31

In the attributes inspector, change the multiplier to 0.7

2016-10-14_07-48-18

Select the Regular Price $99.99 label. Control-drag from this label down and to the left until the content view highlights. Release the mouse button.  Shift select Leading Space to Container Margin, Bottom Space to Container Margin, and Equal Widths.

2016-10-14_07-50-38

Click Add constraints.  Select the width constraint

2016-10-14_07-52-19

Change the width of this label to 60% of the cell. In the attributes inspector, change the Multiplier to 0.6.

2016-10-14_07-56-51

Select Special, then Control-drag Up and to the right. Shift-Select Trailing Space to Container margin and Top Space to Container margin. Click Add Constraints

2016-10-14_07-58-10

Select Special, then Control-drag up and to the right. When you release the mouse button, select Trailing Space to Container margin and Bottom Space to Container Margin.  Click Add Constraints.

2016-10-14_07-58-46

Click the resolverresolver button. In All views in Table View Cell, select Update Frames.

2016-10-14_08-00-13

The cell’s format looks like this, with each label constrained to the corner.

2016-10-14_08-01-04

Connecting Up the Cell

Table view cells need their own class for outlets and actions.  Press Command-N to create a new Cocoa Touch Class Named CustomTableViewCell, subclassing UITableViewCell. Create the file. Once created, return to the storyboard. Select the cell in the document outline.  In the Identity inspector, change the class of the cell to CustomTableViewCell.

2016-10-14_09-33-57

Hide the right ad left inspector panes to give ourself some room for the assistant editor.  Click the assistant editor. You see code for the table view controller. To get to the cell’s controller class is slightly tricky. Xcode does not yet know it is there. You have to manually select it.  In the Assistant editor,  click where it says Automatic. In the menu tree that appears select Manual>PizzaTable>PizzaTable>CustomTableViewCell.swift

2016-10-14_09-39-05

 Make  four outlets for the labels. Control drag from the Menu Item Desc to the code to make an outlet menuItemDescLabel. Control-drag from the Special label to make an outlet specialLabel. Control Drag from Regular price $99.99 to make an outlet regPriceLabel. Control drag the $99.99 label to make the outlet priceLabel. Close the assistant editor.

Functions For the Cell

Open the left inspector and in the navigator go to the CustomTableViewCell.swift file. In the class just under the outlets,  add two constants for color and a discount amount for specials.

let lightGreen = UIColor(red: 0.5, green: 1.0, blue: 0.5, alpha: 1.0)
let lightRed = UIColor(red: 1.0, green: 0.9, blue: 0.9, alpha: 1.0)
let discount = 0.1

The cell can do much of its own setup and formatting, leaving the table view controller a lot cleaner and free of code. Add a function to convert the price to a string in the cell, which also concatenates a string in front of the price.

func string(_ prefix:String, for price:Double) -> String{
    let priceString = String(format: "%2.2f", price)
    return prefix + priceString
}

When an item is on special, it has a 10% discount. I’ll show the discounted price, the regular price and print the word Special on the cell if it is a special. To attract even more attention, I’ll make the background light green.  If not a special, I’ll make the background light red, and show just the price.  Add all that as a function.

func show(isSpecial:Bool,for price:Double){
    if !isSpecial{ //normal
        regPriceLabel.text = ""
        specialLabel.text = ""
        priceLabel.text = string("$", for: price)
        contentView.backgroundColor = lightRed    
    } else { //special discount
        regPriceLabel.text = string("Regular price $", for: price)
        specialLabel.text = "Special"
        priceLabel.text = string("$", for: price * (1.0 - discount))
        contentView.backgroundColor = lightGreen
    }
}

Using the Cell

You’ve set up the custom cell and added a controller to it. Change the Table view controller to use the cell.  Go over the TableViewController.Swift file. Find the tableView(tableview:CellForRowAt index path) method. Comment it out or delete it. I’ll start this from scratch. Add the method in again

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       
}

For  the first line create a cell by dequeuing a reusable cell as you have for any other table view. However, downcast the cell to CustomTableViewCell. Add the code to return the cell

let cell = tableView.dequeueReusableCell(
     withIdentifier: "cell",
     for: indexPath) as! CustomTableViewCell
        
return cell

the rest of the code goes above the return. Add a constant for the row from the index path.

let row = indexPath.row

The model has three arrays for price of a menu item, names of menu items and a bool for specials.  To add the menu item name, just assign it to the outlet menuItemDescLabel's text property.

cell.menuItemDescLabel.text = menuItems.names[row]

That’s all you need for many controls: just passing values to its outlets. You  can use a function from the cell controller.  For the price,  there is the function show(isSpecial:,for price:) in the cell. I call that and it decides how to display the other three labels.

cell.show(
    isSpecial: menuItems.specials[row],
    for: menuItems.prices[row]
)

The final method is this:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
   let row = indexPath.row
   cell.menuItemDescLabel.text = menuItems.names[row]
   cell.show(isSpecial: menuItems.specials[row], for: menuItems.prices[row])
   return cell
}

Configuring the Cell Height

Once you change the cell height, you must tell the table view the row height using the rowHeight property.  In viewDidLoad add the following.

 tableView.rowHeight = 75

This is the same number from the storyboard. I went simple here and just took the 75 points from the storyboard. There are more sophisticated ways to find the height, but I often go this simple route.

Running the Application

Build and run,

2016-10-14_11-53-43

Usually you’ll see two labels with the description and price in each cell. For a special, the cell changes to include the extra two labels, with the discount reflected. If you select an item, the functionality almost works. For a regular item the numbers are correct.

2016-10-14_11-55-37

However a special gives the regular, not the discounted price.

2016-10-14_11-55-57

Stop the simulator.  Change the tableview(tableview:didSelectRowAtIndexPath:) method to this:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let discount = 0.1
    let row = indexPath.row
    let name = menuItems.names[row]
    var price = menuItems.prices[row]
    if menuItems.specials[row]{price *= (1.0 - discount)} //adjust for discount
    delegate.didSelectItem(name: name , price: price)
}

This changes the price if the item is on special. Usually the price is the one given, if not, multiply off the discount. Try running again and the special discounts work.

2016-10-14_12-02-16

I kept the example to a few labels. What you can do in a table view cell is almost everything you can do in a view. You could add more advanced views, animations,  media players, and more as outlets. You can add buttons and switches to the cell, and set actions in CustomTableViewCell to change the cell or the app.

The Whole Code

You can download the completed project here: pizzatable_customcel.zip Below you will find a complete listing of the code.

CustomTableViewCell.swift

//
//  CustomTableViewCell.swift
//  PizzaTable
//
//  Created by Steven Lipton on 10/14/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class CustomTableViewCell: UITableViewCell {

    @IBOutlet weak var menuItemDescLabel: UILabel!
    @IBOutlet weak var specialLabel: UILabel!
    @IBOutlet weak var regPriceLabel: UILabel!
    @IBOutlet weak var priceLabel: UILabel!
    
    let lightGreen = UIColor(red: 0.5, green: 1.0, blue: 0.5, alpha: 1.0)
    let lightRed = UIColor(red: 1.0, green: 0.9, blue: 0.9, alpha: 1.0)
    let discount = 0.1
    
    func string(_ prefix:String, for price:Double) -> String{
        let priceString = String(format: "%2.2f", price)
        return prefix + priceString
    }
    
    func show(isSpecial:Bool,for price:Double){
        if !isSpecial{
            regPriceLabel.text = ""
            specialLabel.text = ""
            priceLabel.text = string("$", for: price)
            contentView.backgroundColor = lightRed
            
        } else {
            regPriceLabel.text = string("Regular price $", for: price)
            specialLabel.text = "Special"
            priceLabel.text = string("$", for: price * (1.0 - discount))
            contentView.backgroundColor = lightGreen
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

TableViewController.swift

//
//  TableViewController.swift
//  PizzaTable
//
//  Created by Steven Lipton on 10/2/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

protocol TableViewControllerDelegate{
    func didSelectItem(name:String,price:Double)
}

class TableViewController: UITableViewController {
    var menuItems = MenuItems()
    var delegate:TableViewControllerDelegate! = nil
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return menuItems.names.count
    }
    
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
        let row = indexPath.row
        cell.menuItemDescLabel.text = menuItems.names[row]
        cell.show(isSpecial: menuItems.specials[row], for: menuItems.prices[row])
        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let discount = 0.1
        let row = indexPath.row
        let name = menuItems.names[row]
        var price = menuItems.prices[row]
        if menuItems.specials[row]{price *= (1.0 - discount)} //adjust for discount
        delegate.didSelectItem(name: name , price: price)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.rowHeight = 75

      }
}

ViewController.swift

//
//  ViewController.swift
//  PizzaTable
//
//  Created by Steven Lipton on 10/6/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit



class ViewController: UIViewController,TableViewControllerDelegate {

    @IBOutlet weak var pizzaOrderedLabel: UILabel!
    @IBOutlet weak var priceLabel: UILabel!
    
    func didSelectItem(name: String, price: Double) {
        pizzaOrderedLabel.text = name
        let priceText = String(format:"Price: %02.2f", price)
        priceLabel.text = priceText
    }
    

    // MARK: - Navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "table"{
            let vc = segue.destination as! TableViewController
            vc.delegate = self
        }
    }


}

MenuItems.swift

//
//  MenuItems.swift
//  SwiftTableViewDemo
//
//  Created by Steven Lipton on 10/1/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class MenuItems:NSObject{
    let names:[String] = [
        "Margherita Pizza","BBQ Chicken Pizza",
        "Pepperoni Pizza","Sausage Pizza",
        "Seafood Pizza","Sausage Deep Dish",
        "Meat Lover's Deep Dish","Veggie Lover's Deep Dish",
        "BBQ Chicken Deep Dish","Mushroom Deep Dish",
        "Tiramisu","Vanilla Ice Cream",
        "Apple Crostata","Hot Fudge Pizza",
        "Soft Drink","Coffee",
        "Espresso","Mineral Water"]
    let prices:[Double] = [
        7.95,11.49,
        8.45,8.45,
        12.75,10.65,
        12.35,10.00,
        16.60,11.25,
        6.50,2.25,6.50,
        9.75,1.25,
        1.25,3.50,3.75
    ]
    let specials:[Bool] = [
        false,true,
        false,false,
        false,false,
        true,false,
        false,true,
        false,false,
        false,true,
        false,false,
        true,false]
}

Leave a Reply

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

WordPress.com Logo

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

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s