Make App Pie

Training for Developers and Artists

How to Make Drill-Down Tables in Swift

drilldown
Table views are a lot like potato chips: You can’t have just one. Often, table views relate to each other in what is often referred to as a drill-down. Drill downs typically take the selected data and provide another list in a table about the selection. It creates a hierarchical selection. You might have a list of car manufacturers for example. From that list you select one manufacturer, which gives you a list of models that manufacturers makes. Click on a model and you might get details about the car. I’ve gotten more questions about drill-down than any other topic, mostly along the lines of “how do I program my table view to…” I’ve given short answers, but I want to give the long one. It really has nothing to do with the table view controllers. It has to do with the models.

For this lesson, we’ll explore ways of getting to that result. This lesson will require skills on building classes, dictionaries, and table views. If you do not understand something you might want to refer to these lessons:

A Look at Some Data

Table views are actually quite simple-minded by design. They can handle data from a simple array and not more than that. Our sophistication with table views happens not in the controller, but in the model. How we handle our data and present it to the controller is key to good design of table views. For most cases, these models are generated from some form of persistent data source, such as a data file, Core Data or SQLite. You may have more then one source of data you want to associate with each other as well. Lets take as an example two data tables. The first table lists foods and the meal it is eaten.

Food Meal
Pizza Lunch
Pasta Lunch
Chocolate Mousse Dessert
Oatmeal Breakfast
Pancakes Breakfast
Peanut Butter and Jelly Lunch
Mole con Pollo Dinner
Hamburger Lunch
Pad Thai Dinner
Paneer Dinner
Chocolate Chip Cookie Snack
Tuna Salad Sandwich Lunch
Bagel Breakfast

The second table lists foods, prices and calorie counts

Food Price Calories
Pizza 4.99 317
Pasta 7.99 325
Chocolate Mousse 6.45 470
Oatmeal 2.95 160
Pancakes 3.95 470
Peanut Butter and Jelly 1.99 319
Pollo con Mole 8.50 860
Hamburger 4.35 250
Pad Thai 7.35 430
Paneer 6.99 100
Sirloin Steak 24.49 165
Chocolate Chip Cookie 2.75 210
Tuna Salad Sandwich 3.25 420
Bagel 1.50 275

What we would like to have is a table view that lists meals

2015-10-01_09-11-50

When we select a meal, the app lists those foods.

2015-10-01_09-19-22

When we select a food, it displays price and calorie information about that food.

2015-10-01_09-19-55

How do we code this?

Set Up the Application

Let’s go through a few different options for doing drill-downs. We’ll need two table views and a view controller. Make a new Xcode single view project DrillDownTables. Make Swift the language and keep the application Universal. Save the project.

The Launchscreen

In the navigator, click the Launchscreen.storyboard file. We’ll need to set up our own launch screen. For most demo and practice apps, I just center a line of text on a label. Drag a label out on the storyboard. Change the text to Drill Down Tables. With the label selected, click the align menu on the bottom right of the storyboard. Click on Horizontally in Container and Vertically in Container. Make sure the values for both are 0. Select Items of New constraints for update frames

center alignment

I usually change the background color as well, this time to #EEFFDD but that is to taste.

Make the Storyboard

We have a bit of work setting up the storyboard, but we only have to do it once for several iterations of the application. Drag a table view controller to the storyboard. In the attributes inspector for the table view controller, under View Controller check on Is Initial View Controller. In the drop down menu, select Editor>Embed in>Navigation Controller . Double-click the navigation bar on the top of the table view controller and title the controller Meals. Drag the original view controller out of the way for now.
Select the tableview and make sure it says Dynamic Prototypes. If you wish you can set a background color, scroll down to view and setting the color. I set a pale yellow (#FFFFCC) for the background.

Select the table view cell. Set the identifier to cell. Set the accessory indicator to Disclosure.

Press Command-N and make a new Cocoa Touch Class subclassing NSObject named MealsTableViewController. I prefer to build table view controllers from scratch to keep a lot of the extra methods in the templates out of the way. NSObject gives a nice blank slate. Save the file and make sure it is in the DrillDownTables folder group. When it opens, change NSObject to UITableViewController so you get this:

class MealsTableViewController: UITableViewController {

}

Go back to the storyboard. Select the Meals View Controller icon. in the identity inspector, set the class to MealsTableViewController.

The Food Table

Drag another table view controller on to the storyboard. Control-drag from the cell in the Meals view controller to the new view controller. Select a show Segue. Set the segue Identifier to Foods.
Select the tableview and make sure it says dynamic prototypes. I’m in a bit of a pastel mood, so I set a peachy color (#FFDDCC) for the background.

Set the table view cell’s identifier cell. Set the Accessory to Disclosure.

Press Command-N and make a new Cocoa Touch Class subclassing NSObject named FoodsTableViewController. Save the file and make sure it is in the DrillDownTables folder group. When it opens, change NSObject to UITableViewController so you get this:

class FoodsTableViewController: UITableViewController {

}

Go back to the storyboard. Select the View Controller icon for the second table. In the identity inspector, set the class to FoodsTableViewController.

The Info View Controller

Control-Drag from the table cell in the Foods view controller to the orphaned view controller from earlier. Select a show Segue. Set the segue Identifier to Info. I set something bluish (#DDEEFF) for the background.
Drag two labels onto the story board, labeling them Calories and Price. Drag Price above Calories. Using autolayout, pin the Price label 20 points up, 0 points left and 0 points right. For Update Frames select Items of New Constraints

Autolayout pin 20-0-0-X

Pin the Calories label the same way: 20 points up, 0 points left and 0 points right. For Update Frames select Items of New Constraints. You should have something like this:

2015-10-02_08-05-01

Press Command-N and make a new Cocoa Touch Class subclassing UIViewController named InfoViewController. Save the file making sure it is in the DrillDownTables folder group. You will only need viewDidload so you can delete everything else in the class.

Go back to the storyboard. Select the view controller icon for this last scene. In the identity inspector, set the class to InfoViewController.

Open the assistant editor. Control-drag from the Price label and make an outlet named priceLabel. Control drag from the Calories label and make an outlet caloriesLabel. Close the Assistant editor.

Make the First Model

Our first goal is to get a working table view. We’ll just list what we need in each of the models first, to make sure the table reads it right.

A List of Meals

Press Command-N and make a new Swift Cocoa Touch file Subclassing NSObject named MealModel. Code the class like this:

class MealModel{
    var meal:[String] = [
        "Breakfast",
        "Lunch",
        "Dinner"]
}

A List of Foods

Our second table is our foods table. For right now we’ll list all the foods in the table to make sure everything in the view controller is working. We’ll use a dictionary to make this table. Press Command-N and make a new Swift Cocoa Touch file Subclassing NSObject named FoodsModel. Code the class like this:

class FoodsModel {
    var foodList:[String:String] = [
        "Pizza":"Lunch",
        "Pasta":"Lunch",
        "Chocolate Mousse":"Dessert",
        "Oatmeal":"Breakfast",
        "Pancakes":"Breakfast",
        "Peanut Butter and Jelly":"Lunch",
        "Mole con Pollo":"Dinner",
        "Hamburger":"Lunch",
        "Pad Thai":"Dinner",
        "Paneer":"Dinner",
        "Sirloin Steak":"Dinner",
        "Chocolate Chip Cookie":"Snack",
        "Tuna Salad Sandwich":"Lunch",
        "Bagel":"Breakfast"
    ]
   func foods(meal:String) -> [String]{
            return Array(foodList.keys)
    }
}    

This model uses a dictionary with the food as a unique key, and the meal as a value. For the table view, we still need an array. We made the function foods to get the array. We don’t use the parameter — yet. I did that so we don’t have to change it later.

We are not going to use the Info view controller just yet. We want to get our tables working first.

Coding our Tables

We have some models to work with. Before we work with the models in more sophisticated ways, it helps to set up the tables and make sure they are working correctly.

Coding the Meals Table

Go to MealTableviewController. Add the model as a property to our blank class:

let meals = MealModel()

We need three data source methods in our table. First add this:

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return meals.meal.count
    }

We have one section and we will take the number of elements of the array meals.meal for the number of rows. Add the cellForRowAtIndexPath data source

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("cell",
        forIndexPath: indexPath)
    let row = indexPath.row
    cell.textLabel?.text = meals.meal[row]
    return cell
    }

The code gets the cell, then the row in the index, since we only have one section. We take the property meal and use the row to determine which element of that array we use.

Coding the Foods Table

Go to FoodsTableviewController. Add the model as a property to our blank class and an array to store the foods from the model:

let foodsModel = FoodsModel()
var meal = "Lunch"
var foods = [String]()

initialize foods by adding a viewDidLoad to the class:

override func viewDidLoad() {
    super.viewDidLoad()
    foods = foodsModel.foods(meal)
}

We need those three data source methods in our table. First add the table size ones:

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return foods.count
}

Add the cellForRowAtIndexPath data source

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
    let row = indexPath.row
    cell.textLabel?.text = foods[row]
        
    if (row % 2) == 0  { //alternate rowcolors
        cell.backgroundColor = UIColor.clearColor()
        cell.textLabel?.textColor = UIColor.blackColor()
    }
    cell.alpha = 0.75
    return cell
    }

This time I added a little spice, as the rows will alternate colors. I decide that by getting the modulo of the row divided by two. It can be either 1 or 0, If 0, I change the color.

Coding the Food Info View

In the Info view controller, we are not yet going to use a model, but set up the controller a bit. Go to the InfoController, add the following property:

var food = "Food Info" // Key 

Change the viewDidLoad to this:

 override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.title = food
}        

We’ll set the food name as our navigation title.
Build and run. On the first screen you should get the three meals.

2015-10-05_06-04-09

Select a meal. You should see all the foods.

2015-10-05_06-04-27

You can select a food, and not much will happen besides a title. We havent programmed our segues yet.

2015-10-05_06-04-44

Adding Segues

Go to MealsTableViewController. Add a prepareForSegue method

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "Foods"{
        let vc = segue.destinationViewController as! FoodTableViewController
        let selectedMeal = meals.meal[(tableView.indexPathForSelectedRow?.row)!]
        vc.meal = selectedMeal
        vc.navigationItem.title = selectedMeal
     }
}

We send the selected meal to the next controller, setting its title and the meal property. Go to the FoodsTableViewController. Add this prepareForSegue method:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    let selectedRow = (tableView.indexPathForSelectedRow?.row)!
    if segue.identifier == "Info"{
        let vc = segue.destinationViewController
             as! InfoViewController
        vc.food = foods[selectedRow]   
    }
}  

In both of these controllers, we use the indexPathForSelectedRow property to get the index path of the row selected, and use the data from that row to send to the destination controller.
Build and run. Our selection reflect on the next view’s navigation title, but not the food selection.

2015-10-05_06-14-49

Select a food to go to the Info scene and the food shows on the Navigation bar title

2015-10-05_06-15-35

Iteration 1: Coding with a Dictionary

Much of what we need is already set up. The MealsTableViewController is already sending the selected meal to the FoodsTableViewController. We have to do something with it. The long list of foods needs to be a short list of foods for only that meal. Comment out the current foods and replace with this:

//linear search for all food with a given meal
func foods(meal:String) -> [String]{
    var shortList:[String] = []
    for food in Array(foodList.keys){
    	if foodList[food] == meal{
            shortList += [food]
        }
    }
    return shortList
} 

This performs a linear search for all foods for that meal. It returns the list for that have meal for a value. Build and run. Select Breakfast. You get breakfast foods.

2015-10-05_06-20-17

A One to Many Table

Notice something about our table though: We have a one to one relationship between the meal and the food. Using this code I can only have a Hamburger for lunch or dinner, but not both.

The key of the dictionary is the food, not the meal. We need a unique key for the meal, and then a list of foods. In the MealsModel.swift file, comment out the current model and add this for the class:

class MealModel {
    var mealList = [String:[String]]()
}

This makes a dictionary where we have a meal for a key and a array of foods associated with the food.

To get the meals, we can add the following computed property

var meal:[String]{
    get{
        return Array(mealList.keys)
	 }
}

To make it easier to add items to the model, add an addItem method:

func addItem(meal:String,foods:[String]){
    mealList[meal] = foods
}

We’ll initialize the class with slightly different data. Add the following:

init(){
        //what we do here would usually be a database or file access
    addItem("Breakfast",
        foods: [
            "Oatmeal",
            "Pancakes",
            "Bagel"
        ])

    addItem("Lunch",
        foods: [
             "Pizza",
             "Pasta",
             "Peanut Butter and Jelly",
             "Tuna Salad Sandwich", 
             "Hamburger"])
    addItem("Dinner",
       foods: [
             "Mole con Pollo",
             "Pad Thai",
             "Paneer",
             "Sirloin Steak",
             "Pizza",
             "Pasta",
             "Hamburger"
       ])
    addItem("Snack",
       foods: [
            "Chocolate Chip Cookie",
            "Pizza"
        ])
 }

We have a list of foods. We added Snack, and removed the orphan dessert chocolate mousse. A hamburger is now a lunch and dinner food. Pizza is a lunch, dinner, and nack food. We have a one to many relationship.

We need only one more thing to complete this new class. Add the following code:

func foods(meal:String) -> [String]{ //for use with the model
	if let list = self.mealList[meal]{
		return list
	} else {
		return []
	}
}

Notice the code is a lot simpler in this version of foods, since we use a dictionary property. We just check if the dictionary exists using optional chaining then return our food list. If no entry, return an empty array

Changing the FoodModel

The class FoodModel has a lot less to do. I could remove it completely, but in a real application there may be things I do to a selected list of foods that I won’t do to the entire list. Comment out the current FoodModel class and add this class after it

/* Second Iteration of the model */
class FoodsModel{
    var foodList:[String] = []
    func foods(meal:String) ->[String]{
        return foodList
    }
}

We have in this model version a string array for the list of foods. If we assume MealsModel sends the list of selected foods to this model, we only have to return the value.

Coding the Tables

The beautiful thing about MVC is that if we use the same properties and methods in two classes we can swap them out easily. Our new versions of FoodsModel and MealsModel need very few changes. FoodsModel works exactly the same from the table view controllers view, though the meal parameter is once again redundant. We have to change only one thing in the meals table for these models. We added the foods function to the MealsModel. We will use that to load the FoodsModel in our prepareForSegue like this:

vc.foodsModel.foodList = meals.foods(selectedMeal)

In the MealsTableViewController, change prepareForSegue to this:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "Foods"{
    let vc = segue.destinationViewController as! FoodsTableViewController   
    let selectedMeal = meals.meal[(tableView.indexPathForSelectedRow?.row)!]
    vc.meal = selectedMeal
    vc.navigationItem.title = selectedMeal
    vc.foodsModel.foodList = meals.foods(selectedMeal)
}

Build and run. Select Lunch,

2015-10-05_07-13-20

Go back to the menu and Select Dinner

2015-10-05_12-50-28

Hamburger and pizza show up in both.

Coding the Info Model

We’ve been ignoring the last view controller. While we’ve been drilling down, we have not gotten to any detail about our foods. At some point we will need to work with data. In our example, it is calorie and price information. In Xcode, press Command-N and make one more class subclassing NSObject called FoodInfoModel. Above the FoodInfoModel class, add this class:

class FoodItemInfo{ // the record or row of the database
    var name = ""
    var price = 0.00
    var calories = 0
    init(name:String,price:Double,calories:Int){
        self.name = name  //primary key
        self.price = price
        self.calories = calories
    }
}

If you think in database terms, this is our record or row. We’ll use this in the dictionary we’ll create in FoodInfoModel to hold our data. Add this to the FoodInfoModel class:

var list = [String:FoodItemInfo]()
    
func addItem(key:String,price:Double,calories:Int){
    let foodItem = FoodItemInfo(
        name: key, 
        price: price, 
        calories: calories)
    list[key] = foodItem  //this modifies or adds to the file
}    

He’s our dictionary of info with a key of a food item’s name. We have one seeming redundancy: name is the key, and is a property in the FoodItemInfo class. I may pass the FoodItemInfo somewere without the key, and so I’d like to keep the name handy. The method addItem adds the record for a food to our dictionary. I could do use FoodItemInfo’s initializer for each food item to load my dictionary:

list["Pizza"] = FoodItemInfo("Pizza", price: 4.99, calories: 317)

However, addItem is better documentation and is less keystrokes. Add the following to the end of the FoodInfoModel:

init(){
    // this would normally be a loaded file
    // or Database from SQL or Core Data
    addItem("Pizza",
    	price: 4.99,
    	calories: 317)
    addItem("Pasta", 
    	price: 7.99, 
    	calories: 325)
    addItem("Chocolate Mousse", 
    	price: 6.45, 
    	calories: 470)
    addItem("Oatmeal", 
    	price: 2.95, 
    	calories: 160)
    addItem("Pancakes", 
    	price: 3.95, 
    	calories: 470)
    addItem("Peanut Butter and Jelly", 
    	price: 1.99, 
    	calories: 319)
    addItem("Pollo con Mole", 
    	price: 8.50, 
    	calories: 860)
    addItem("Hamburger", 
    	price: 4.35, 
    	calories: 250)
    addItem("Pad Thai", 
    	price: 7.35, 
    	calories: 430)
    addItem("Paneer", 
    	price: 6.99, 
    	calories: 100)
    addItem("Sirloin Steak", 
    	price: 24.49, 
    	calories: 165)
    addItem("Chocolate Chip Cookie", 
    	price: 2.75, 
    	calories: 210)
    addItem("Tuna Salad Sandwich", 
    	price: 3.25, 
    	calories: 420)
    addItem("Bagel", 
        price: 1.50,
    	calories: 275)
}

Our data will now load when we initialize the class.

Coding The Info View Controller

We’ll use the key:value coding of dictionareis again to find and access information about a certain food. Dictionary access returns an optional value, so we’ll check for nil using optional chaining. If there is a value, we’ll display it. If not, we’ll display that it wasn’t found in our database. Add the following to InfoViewController:

var foodInfoModel = FoodInfoModel() //our model
func displayFoodInfo(){
    if let foodInfo = foodInfoModel.list[food]{
       caloriesLabel.text = String(format:"%i Calories",foodInfo.calories)
       priceLabel.text = String(format:"Price: %1.2f",foodInfo.price)
   } else {
      caloriesLabel.text = "Not Found"
      priceLabel.text = "Not Found"
   }
 }

We’ll display the info when we load the view controller. Change viewDidLoad to this:

override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.title = food
    displayFoodInfo()
 }
    

Build and run. In Lunch, select Peanut butter and jelly

2015-10-05_07-49-08

This demo app worked with literal values we added in the init() of a class. In a real app, it’s likely that these would be SQLite databases, files or Core Data. Actually implementing them in an app is more than few lessons in themselves, so I went with this easier method. Once you understand this concept, you might want to learn about persistent storage of some kind.

The Whole Code

Note in the code below I did not format for web readability. I assume this will be code cut and pasted to Xcode, so left it in the standard format, with the exception of adding data to the arrays and dictionaries.

MealModel.swift

//
//  MealModel.swift
//  DrillDownTableDemo
//
//  Created by Steven Lipton on 10/5/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit
/* first iteration of model
class MealModel{
 
    //first iteration for testing
    var meal:[String] = [
        "Breakfast",
        "Lunch",
        "Dinner"]
} */

/* Second iteration of model */
class MealModel {
    var mealList = [String:[String]]()
    var meal:[String]{
        get{
            return Array(mealList.keys)
        }
    }
    func addItem(meal:String,foods:[String]){
        //adds an item to the dictionary if not there
        //side effect: changes current elements in dictionary
        mealList[meal] = foods
    }
    
    func foods(meal:String) -> [String]{ //for use with the model
        if let list = self.mealList[meal]{
            return list
        } else {
            return []
        }
    }

    /* Load data into the model
       Note that this would normally be some external load
       from a file or database
       For this lesson we are loading it literally
    */
    init(){
        addItem("Breakfast",
            foods: [
                "Oatmeal",
                "Pancakes",
                "Bagel"
            ])
        addItem("Lunch",
            foods: [
                "Pizza",
                "Pasta",
                "Peanut Butter and Jelly",
                "Tuna Salad Sandwich",
                "Hamburger"
            ])
        addItem("Dinner",
            foods: [
                "Mole con Pollo",
                "Pad Thai",
                "Paneer",
                "Sirloin Steak",
                "Pizza",
                "Pasta",
                "Hamburger"
            ])
        addItem("Snack",
            foods: [
                "Chocolate Chip Cookie",
                "Pizza"
            ])
    }
    
}

FoodsModel.swift

//
//  FoodsModel.swift
//  DrillDownTableDemo
//
//  Created by Steven Lipton on 10/5/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit
/* First Iteration of the model
class FoodsModel {
    var foodList:[String:String] = [
        "Pizza":"Lunch",
        "Pasta":"Lunch",
        "Chocolate Mousse":"Dessert",
        "Oatmeal":"Breakfast",
        "Pancakes":"Breakfast",
        "Peanut Butter and Jelly":"Lunch",
        "Mole con Pollo":"Dinner",
        "Hamburger":"Lunch",
        "Pad Thai":"Dinner",
        "Paneer":"Dinner",
        "Sirloin Steak":"Dinner",
        "Chocolate Chip Cookie":"Snack",
        "Tuna Salad Sandwich":"Lunch",
        "Bagel":"Breakfast"
    ]
    /*
    func foods(meal:String) -> [String]{
        return Array(foodList.keys)
    }
*/
    //linear search for all food with a given meal
    func foods(meal:String) -> [String]{
        var shortList:[String] = []
        for food in Array(foodList.keys){
            if foodList[food] == meal{
                shortList += [food]
            }
        }
        return shortList
    }
}*/


/* Second Iteration of the model */
class FoodsModel{
    var foodList:[String] = []
    func foods(meal:String) ->[String]{
        return foodList
    }
}

FoodInfoModel.swift

//
//  FoodInfoModel.swift
//  drillDownTable
//
//  Created by Steven Lipton on 10/1/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit

class FoodItemInfo{ // the record or row of the database
    var name = ""
    var price = 0.00
    var calories = 0
    init(name:String,price:Double,calories:Int){
        self.name = name  //primary key
        self.price = price
        self.calories = calories
    }
    

}

class FoodInfoModel{ // the file of the database
    var list = [String:FoodItemInfo]()
    
    func addItem(key:String,price:Double,calories:Int){
        let foodItem = FoodItemInfo(name: key, price: price, calories: calories)
        list[key] = foodItem  //this modifies or adds to the file
    }
    
    init(){
        // this would normally be a loaded file
        // or Database from SQL or Core Data
        addItem("Pizza",
           price: 4.99, 
           calories: 317)
        addItem("Pasta", 
           price: 7.99, 
           calories: 325)
        addItem("Chocolate Mousse", 
           price: 6.45, 
           calories: 470)
        addItem("Oatmeal", 
           price: 2.95, 
           calories: 160)
        addItem("Pancakes", 
           price: 3.95, 
           calories: 470)
        addItem("Peanut Butter and Jelly", 
           price: 1.99, 
           calories: 319)
        addItem("Pollo con Mole", 
           price: 8.50, 
           calories: 860)
        addItem("Hamburger", 
           price: 4.35, 
           calories: 250)
        addItem("Pad Thai", 
           price: 7.35, calories: 430)
        addItem("Paneer", 
           price: 6.99, 
           calories: 100)
        addItem("Sirloin Steak", 
           price: 24.49, 
           calories: 165)
        addItem("Chocolate Chip Cookie", 
           price: 2.75, 
           calories: 210)
        addItem("Tuna Salad Sandwich", 
           price: 3.25, 
           calories: 420)
        addItem("Bagel", 
           price: 1.50, 
           calories: 275)
    }

}

MealsTableViewController.swift

//
//  MealsTableViewController.swift
//  DrillDownTableDemo
//
//  Created by Steven Lipton on 10/4/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit

class MealsTableViewController: UITableViewController {
    let meals = MealModel()
    
    //MARK:  Table View Datasources
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return meals.meal.count
    }
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell",
            forIndexPath: indexPath)
        let row = indexPath.row
        cell.textLabel?.text = meals.meal[row]
        return cell
    }
    
    //MARK: Life Cycle 
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "Foods"{
            let vc = segue.destinationViewController as! FoodsTableViewController   
            let selectedMeal = meals.meal[(tableView.indexPathForSelectedRow?.row)!]
            vc.meal = selectedMeal
            vc.navigationItem.title = selectedMeal
            vc.foodsModel.foodList = meals.foods(selectedMeal)
        }
    }

}

FoodsTableViewController.swift

//
//  FoodsTableViewController.swift
//  DrillDownTableDemo
//
//  Created by Steven Lipton on 10/4/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit

class FoodsTableViewController: UITableViewController {
    let foodsModel = FoodsModel()
    var meal = "Lunch"
    var foods = [String]()
    
    //MARK: Table View Data Sources
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return foods.count
    }
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
        let row = indexPath.row
        cell.textLabel?.text = foods[row]
        
        if (row % 2) == 0  { //alternate rowcolors
            cell.backgroundColor = UIColor.clearColor()
            cell.textLabel?.textColor = UIColor.blackColor()
        }
        cell.alpha = 0.75
        return cell
    }
    
    
    //MARK: Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        foods = foodsModel.foods(meal)
    }
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        let selectedRow = (tableView.indexPathForSelectedRow?.row)!
        if segue.identifier == "Info"{
            let vc = segue.destinationViewController
                as! InfoViewController
            vc.food = foods[selectedRow]
        }
    
    }
}

InfoViewController.swift

//
//  InfoViewController.swift
//  DrillDownTableDemo
//
//  Created by Steven Lipton on 10/5/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit

class InfoViewController: UIViewController {

    @IBOutlet weak var priceLabel: UILabel!
    
    @IBOutlet weak var caloriesLabel: UILabel!
    
    
    var food = "Food Info"
    var foodInfoModel = FoodInfoModel() //our model
    func displayFoodInfo(){
        if let foodInfo = foodInfoModel.list[food]{
            caloriesLabel.text = String(format:"%i Calories",foodInfo.calories)
            priceLabel.text = String(format:"Price: %1.2f",foodInfo.price)
        } else {
            caloriesLabel.text = "Not Found"
            priceLabel.text = "Not Found"
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = food
        displayFoodInfo()
    }

}

4 responses to “How to Make Drill-Down Tables in Swift”

  1. […] this might be pretty, in other table view lessons we’ve done this.  In practice, we will have circumstances that will use one cell instead of […]

  2. Hello,
    I stuck here;
    The Info View Controller

    Control-Drag from the table cell in the Foods view controller to the orphaned view controller from earlier. Select a show Segue. Set the segue Identifier to Info. I set something bluish (#DDEEFF) for the background.

  3. This tutorial seems old, I am getting a lot of errors (syntax-wise) following this. Could this tutorial be updated? it would be very helpful!

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 )

Facebook photo

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

Connecting to %s

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

%d bloggers like this: