Using Tableviews in Subviews

2016-10-07_05-44-02Have you ever wanted to have a table view and other labels on the same scene? How about two table views on the same scene? Many developers don’t know how to do that.  On any given scene on a storyboard, we’re stuck with just a table view, though with some advanced use you can add headers and footers. I have good news — you can re-size your table views to add other controls. There is a special UIView called a container view. In this lesson we’ll add a container view to make a pizza ordering app show our order selected from a table view.

Set Up the Project

You’ll need a functioning table view for this project. I’ve provided one for you in the starter project you can download here: pizzatable_view_start.zip. You can find how to create this table in the post Introducing Table Views in Swift 3.0 I added one more thing to the starter file if you decide to create it on your own. I added this image of a pizza

pizza_table_image

If you are not working from the starter file, download the image and add it to the Assets.xcassets folder:
2016-10-06_06-25-37

Adding a Background Image

I’ll add the pizza image to the background image. From the object library, drag an image view on the storyboard. You’ll find you have a problem: You can’t place it to make a background for the entire view. It scrunches itself up to either be a cell background or the area above the table view:

2016-10-06_06-40-07

Tableviews are space hogs. They take the whole view and we can’t change that to place anything else on the storyboard.

Changing the Storyboard

The solution is to use a container view. Container views are special subviews that contain a view controller.

Drag a new  view controller to the storyboard. In the attributes inspector check on Is Initial View Controller.
2016-10-06_08-36-39

Drag an image view to the new view controller. Click the pinMenuButton  on the lower right of the storyboard for the pin auto layout menu. Click off Constrain to Margins. Set all four pins to 0 points. Update frames with the new Constraints like this:

2016-10-06_08-47-53

This will stretch the image view over the window for all devices. In the attribute inspector, set the image for the Image view to pizza_table_image. Set the content mode to Aspect Fill 2016-10-06_08-53-19

Find the container view in the object library.

2016-10-06_08-56-32

Drag a container view  over the pizza background image. It leaves a UIView, a Storyboard Embed segue and a ViewController. (Note: I changed the background color of the view controller  to gray for visibility. )

2016-10-07_06-14-07

Generally you place  an object on the view controller like a label or button, but for table view controllers you either have to do a lot more coding and manually create the table or replace the view controller with a table view controller. I’ll replace the view controller completely in this tutorial since it is sitting right there waiting for us. In the document outline, select the View Controller Scene connected to the container view and press delete.  The segue and  view controller disappear.

Control-drag from the Container View to the Table View controller. Select an Embed segue

2016-10-06_09-11-12

The  table view controller changes size to fit the view. You’ll find the tableview is too small to be useful.

2016-10-07_06-17-06

For our application, I’d like the bottom third of the app to be the table, and leave the top third for some other information. I’ll use some auto layout to do that.  Select the Container view. Using the pin menu pinMenuButton, pin left, right, and bottom 10 points, leaving Update Frames as None.

2016-10-06_09-27-21

Add the constraints.  I didn’t set a top so I can set the hight relative to the view controller’s height. In the document outline, Control-drag from the Container View to the View

2016-10-06_09-30-15

Select Equal heights in the menu that appears.  You’ll see a long constraint on the outside of the controller, probably with the number -539 in it if you are previewing an iPhone 6s.  That’s the Equal Heights constraint. The table view will be the same height as a the superview.  Click once on the line of this constraint. The attributes inspector should show the Equal Heights Constraint attributes.  Change the Multiplier to 0.6. This sets the height of container view to 60% of the height of the view.

2016-10-06_09-42-19

Select the Container View in the document outline.  Click the auto layout resolve button resolver button. Select in the Selected Views portion Update Frames. We have the view set, and the table view controller resizes.

2016-10-06_09-49-12

Since the table view is configured, build and run.  The table is  only on the lower part of the view, with margins see the pizza behind it.

2016-10-06_10-14-30

Since I used auto layout, You can hit Command- Right Arrow and it works in landscape too.

2016-10-06_10-20-09

Press Command-Left Arrow to return to Portrait.

Display a Selection

The app has some empty real estate at the top. I’ll add Two larger labels which tell us the menu item we selected. I’ll add the labels to the top, and then connect up the tableview to report back the selection to those labels.

With a busy image background like this, I prefer to place a translucent UIView in that top third, and then place the labels in it for readability.   Stop the app and go to the storyboard. Drag a View (not a view controller) above the table.  In the attributes inspector, click on the rectangular color swatch on the Background button to get the color picker. Select a light color such as white #FFFFFF.  You can also match the color closer to the image by using the eyedropper in the RGB color picker  and select a color from the background image. I found the cheese was  #E2E8FC so I picked that.  Set the Opacity to 70%.

Using the Autolayout pin menupinMenuButton , pin the new  view 10 points on all sides and set  Update Frames with Items with  New Constraints.

Drag two labels into the translucent view one above the other.  If you drop them into the translucent view they become subviews automatically.  On the upper label, Change the text to Pizza Ordered, and the font to Title 1. In the lower label, change the text to 00.00. Right justify the text and set the label’s font to Title 2.

Select the Pizza ordered label again. With the pin menu pinMenuButton, Pin it 10 up, 10 left and 10 right, updating for the new constraints. For the price label, pin it  10 up, 10 left and 10 right, again updating for new constraints.  If all done correctly, It should look like this:

2016-10-06_10-45-27

Press Command-N on the keyboard to get a new File. Make a new  Cocoa Touch Class named ViewController, subclassing UIViewController. Keep the language Swift. Create the new controller. Go back to the storyboard and select the view controller scene. In the identity inspector,  change the class of the view controller to ViewController.

Open the assistant editor.  Control drag to add two outlets for the labels in the code named pizzaOrderedLabel, and priceLabel.

Close the assistant editor and go to the TableViewController.swift code. The table view controller is working independently on top of the view controller. They communicate as easily as if they were separate controllers linked by segues, and I treat them like that. I’ll set up a delegate to change the label. I’m not going to explain delegation here. You can read about delegation in the article Why do we need Delegates.

Above the TableViewController class, add the protocol:

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

In the TableViewController class add the delegate property

var delegate:TableViewControllerDelegate! = nil

Add the selection method for the table view, which calls the delegate. For more about this method you can check my -in depth article on table view controllers

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let row = indexPath.row
        let name = menuItems.names[row]
        let price = menuItems.prices[row]
        delegate.didSelectItem(name: name , price: price)
    }

Go to ViewController.swift. Adopt the protocol

class ViewController: UIViewController,TableViewControllerDelegate

Add the required method, using the values of name and price to set the labels.

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

Set the delegate in prepare( for segue:) for an identifier table

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

I haven’t yet set the segue identifier. Go to the storyboard. Click on the segue and set the identifier to table.
2016-10-07_05-41-01

Build and run. Select an item from the menu, and it appears on the top.

2016-10-07_05-44-02

Container views could be used for any set of views you want a separate view controller for. You could put several on a regular width size class next to each other. Generally though, it’s easier to handle labels and the like as subviews. With table views and collection views, however, they make a lot of sense. You can use controls that normally steal the full view and limit their size. There’s also no program limit to how many of these you can have on a view. while a compact width device can probably handle two at most, regular width devices could easily have more. While simple, container views can provide a flexibility you don’t normally have with a single view.

The Whole Code

Here is the code for the project. You can also download the project here:pizzatable_view.zip

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]
}


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


}

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)
        let row = indexPath.row
        cell.textLabel?.text = menuItems.names[row]
        let price = String(format:"%2.2f", menuItems.prices[row])
        cell.detailTextLabel?.text = price
        if menuItems.specials[row]{
            cell.backgroundColor = UIColor.green
        } else {
            cell.backgroundColor = UIColor.white
        }
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let row = indexPath.row
        let name = menuItems.names[row]
        let price = menuItems.prices[row]
        delegate.didSelectItem(name: name , price: price)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

      }
}

One thought on “Using Tableviews in Subviews”

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