Beginning Container Views in iOS

What are container views

Container views allow a view to have child views which are in turn managed by their own view controllers. The view controllers such as Navigation Controller and Tab Bar Controller which are available in iOS by default fall under this category.

Today we will create a simple app which illustrates how to use container views and also how to use delegation to enable communication between the parent and the child view controllers.

The finished app will look like the pic below.

finished app

In the above app, the view that displays score is in its own container view and the tab bar controller is in a separate container view. When the question is answered correctly, the score will be updated accordingly.

This tutorial is written in Swift 3.0. Lets dive into coding.

Lets Get Started

Create a new Xcode project using Xcode->File->Project. Choose Single View Application and tap on Next.
single view application

Enter the Product Name, Organization Name and Organization Identifier. Choose Team as None, Language as Swift and Devices as Universal and tap on Next

new project

Choose a location to save the project and tap on "Create".

UI Design

Lets create the custom class for the view controller. Tap on ViewController.swift in the document outline and rename it to BaseViewController.swift

create class

Also change the class name from ViewController to BaseViewController in the source code

rename viewcontroller

Tap on the View Controller in the storyboard, then tap on Identity inspector and type BaseViewController as the class name and press enter

set custom class name

Adding Container View

Lets move on to adding container view to the story board. Choose Container View from the object library.

add container view from library 2

Drag and drop the container view from the object library on to the storyboard.

add container view from library 1

Tap on container view and add the following constraints using Add New Constraints at the bottom right corner. Set the top, left and right constraints to 0, uncheck Constraint to margins and tap on Add 3 constraints.

container view constraints 1

We have basically added 3 constraints that pin the container view to the top, left and right edges of the super view.

At this point you will get an error stating Container View Needs constraint for height. Lets add the missing constraint and make Xcode happy:).

Control + drag from the container view to the super view in the document outline and choose equal heights

container view constraints 2

container view constraints 3

Double tap on the newly added Equal Height constraint in the size inspector and change the multiplier to 1:5 and hit enter. This means that the container view will have one fifth of the height of its parent.

container view constraints 4

container view constraints 5

Tap on the base view controller, then tap update frames in the bottom right corner to update the frames in accordance with the constraints.

view update frames

After updating constraints, the storyboard will look like this.

view after update frames

Congrats, you have successfully created your first container view. As you can see above, the container view is backed by its own view controller and it resizes in accordance with the size of the container view.

Lets create the custom class for this container view controller. Tap on File->New->File... and choose Cocoa Touch Class and tap on Next.

new cocoa touch class 1

Type ScoreViewController in Class, UIViewController in Subclass, uncheck "Also create XIB file" and choose the language as Swift. Tap Next followed by Create

new cocoa touch class 2

Now tap on the container view's view controller in the storyboard and choose identity inspector from the top right.

set custom class

Type ScoreViewController in the Class Field and press Enter.

Now lets move on and create the bottom container view. Drag and the drop a second container view from the object library on to the storyboard.

create bottom container view

Now we have to delete the view controller of the bottom container view and replace it with a tab bar controller.

Go ahead and select the view controller of the bottom container view from the document outline. It will be named as View Controller Scene.

delete container view controller

Tap on it and press delete.

Drag a Tab Bar Controller from the Object library on to the story board

tab bar controller

Control + drag from the bottom container view to the tab bar controller and choose embed from the menu that pops up. The tab bar controller resizes itself to the size of the container view

embed tab bar in container view 1

embed tab bar in container view 2

Autolayout for the bottom container view

Now lets add the constraints for the bottom container view. Tap on the bottom container view in the storyboard to select it. Tap on Add New Constraints in the bottom right corner. Make the top, bottom, left and right spacing to zero, uncheck Constrain to margins, select Items of New Constraints in Update frames and tap on Add 4 Constraints

bottom container view constraints 1

The storyboard will look like this after adding constraints.

bottom container view constraints 2

Lets change the background color of the top container view to light gray and run the app. Select the root view of the SourceViewController and change its background color to light gray.

Run the app and you will see a screen like the one below.

curren app status

Score View Design

Drag a label from the Object library to Score View Controller Scene and change its text to Score

score label creation

Set the left, top and bottom constraints of the score label to zero. Uncheck Constrain to Margins, choose Items of New Constraints in Update Frames and then tap on Add 3 Constraints

score label constraints

Drag one more label to the right of the score label and set its text to 0.

zero label creation

Control + drag from zero to score and select horizontal spacing. You will get a few errors, which we will fix in the next step.

zero label constraints 1

zero label constraints 2

Select the zero label and the following constraints. Set top and bottom to 0, uncheck constrain to margins, select All frames in container and tap on Add 2 constraints

zero label constraints 3

Select the zero label, tap on size inspector, select the Leading space To: Equals: constraint, tap on edit, change its value to 10 and press enter.

zero label constraints 4

Tab Bar UI Design

Lets set the custom class for the first tab. Tap on File->New->File... Select Cocoa Touch Class. Enter the class name as StartupQuizViewController, subclass as UIViewController, uncheck Also create XIB File, choose language as Swift and tap on Next Followed by Create

Tap on Item 1 in the story board and set the custom class as StartupQuizViewController in the Identity Inspector

set custom class

Select Item 1 Tab bar item in the document outline, tap on Attributes Inspector and set the Title as Startup Quiz and press Enter

tab bar title change

Drag and drop a label on to the Startup Quiz tab. Change its text to Who is the founder of apple. Select the label and tap Align in the bottom right corner. Check Horizontally Center in Container and Vertically Center in Container, Select All Frames in Container as Update Frames and Tap on Add 2 Constraints

question label

Drag and drop 2 buttons from the Object Library on to the Startup Quiz tab one below the other below the label and change their text to Steve Jobs and Elon Musk

answer labels

Control + drag from Steve jobs button to Who is the founder of apple label. Select both Vertical Spacing and Center Horizontally from menu and tap on Add Constraints.

answer constraints 1

Select Steve Jobs button and change the Top Space to: Equals constraint to 8 in the size inspector and press Enter

answer constraints 2

Similarly Control + drag from Elon Musk button to Steve Jobs button and select both Vertical Spacing and Center Horizontally from the menu and tap on Add Constraints.

Select Elon Musk button and change the Top Space to: Equals: constraint in the size inspector to 8.

Tap on Editor->Update Frames menu to update all frames.

Lets quickly finish the second tab bar's design. Its similar to the first one.

Lets create the custom class for the second tab bar. Tap on File->New->File... and choose Cocoa Touch Class and tap on Next. Enter ScienceQuizViewController in Class, UIViewController in subclass, uncheck Also create XIB File and choose the Language as Swift and tap on Next followed by Create

Select the second tab in the document outline, it will be named as Item 2 and change the class to ScienceQuizViewController in the identity inspector.

set custom class

Select the tab bar item of the second tab in the document outline and change its title to Science Quiz in the attributes inspector and press Enter

tab bar bottom title change

Drag and drop a label from the object library to Science Quiz scene and and change its text to Who Invented the Electric Bulb

Select the label and tap Align in the bottom right corner and select Vertically Center in Container, Horizontally Center in Container, update frames as All Frames in Container and tap Add 2 Constraints

tab bar constraints 1

Drag 2 buttons below the label one below the other. Change their titles to Thomas Edison and Albert Einstein

add answer buttons

Lets add constraint to these two buttons. Control + drag from Thomas Edison To Who Invented Electric Bulb and choose both Vertical Spacing and Center Horizontally from the menu and tap on Add Constraints

Select Thomas Edison in the storybard, tap on size inspector and edit Top Space To: Equals: to 8 and press Enter

second quiz answer button constraints 1

Control + drag from Albert Einstein to Thomas Edison and select both vertical spacing and center horizontally in container and tap Add Constraints

Select Albert Einstein and change the Top Space To: Equals: constraint to 8 in the size inspector.

second quiz answer constraints 2

Select science quiz scene from the document outline and tap Editor->Update Frames to update all frames.

Go ahead and run the application. You should be able to see 2 tabs with a score view on the top.

Communication between Container Views

To update the score when the correct option is selected, we need to inform the parent view controller about it and then accordingly update the child view from the parent view. We can achieve this using delegation. In simple terms delegation allows 2 objects to communicate with one another blindly without know about each other.

Create a new swift file by tapping File->New->File... and select Swift File from the list and Tap next. Name the file as ScoreDelegate and tap on create.

Enter the following code in
ScoreDelegate.swift

import Foundation  
protocol ScoreDelegate:class {  
    func addOneToScore()
}

This delegate function will be used by the child view controllers StartupQuizViewController and ScienceQuizViewController to tell the parent BaseViewController to add one to the score.

Now that the delegate is ready, lets use it to pass data from the child view controllers.

Select the Startup Quiz Scene from the document outline and tap on Show Assistant Editor. Then select Automatic->StartupQuizViewController.swift.

Control + drag from steve jobs button to StartupQuizViewController and choose Action as connection and type correctAnswer in the name and then tap on connect.

create IBAction 1

create IBAction 2

This will create a Action like below,

@IBAction func correctAnswer(_ sender: Any) {
}

Lets create a variable for the delegate now. Add the following code to the first line of the StartupQuizViewController class

weak var delegate:ScoreDelegate?  

Now that we have the delegate ready, lets call it when someone taps on the correct answer. The following is the full code of StartupQuizViewController after calling the delegate method inside the correctAnswer Action

StartupQuizViewController.swift

//StartupQuizViewController.swift 
import UIKit

class StartupQuizViewController: UIViewController {  
    weak var delegate:ScoreDelegate?

    @IBAction func correctAnswer(_ sender: Any) {
        delegate?.addOneToScore()
    }
}

You might have viewDidLoad and didReceiveMemoryWarning methods in your class. You can leave them if you wish. I deleted them, since they are not going to be used.

Since Elon Musk is the wrong answer, we don't do anything when someone taps on it :)

Lets move on to the code for the Science Quiz tab and do the same job of calling the delegate when some one answers the science quiz correctly.

Select the science quiz scene in the document outline and tap on the Show Assistant Editor. Then select Automatic->ScienceQuizViewController.swift

Control + drag from Thomas Edison button to ScienceQuizViewController.swift. Select connection as Action and type correctAnswer in the Name field and tap connect. This will create the action below.

@IBAction func correctAnswer(_ sender: Any) {
}

Now lets create the delegate var and call the addOneToScore method when the correct answer is tapped. This is identical to the StartupQuizViewController. Here is the complete code.

ScienceQuizViewController.swift

//ScienceQuizViewController
import UIKit  
class ScienceQuizViewController: UIViewController {  
    weak var delegate: ScoreDelegate?

    @IBAction func correctAnswer(_ sender: Any) {
        delegate?.addOneToScore()
    }

}

Since Albert Einstein is the wrong answer, we don't do anything when that button is tapped.

The next step is to make BaseViewController as the delegate of these two view controllers so what it will know when to update the score.

Since the BaseViewController is the parent class for the Tab bar controller that holds StartupQuizViewController and ScienceQuizViewController view controllers, by overriding prepare(for segue: UIStoryboardSegue, sender: Any?) we can get the reference of the child tab bar controller and then we can set the BaseViewController as the delegate of StartupQuizViewController and ScienceQuizViewController. Move to BaseViewController and write the below code in that.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {  
    if let baseTabBarVC = segue.destination as? UITabBarController {
        if let firstTab = baseTabBarVC.viewControllers?.first as? StartupQuizViewController {
            firstTab.delegate = self
        }
        if let secondTab = baseTabBarVC.viewControllers?[1] as? ScienceQuizViewController {
            secondTab.delegate = self
        }
   }
}

Now lets create an extension and implement the delegate method. In BaseViewController add the following code

extension BaseViewController: ScoreDelegate {  
    func addOneToScore() {
        print("score to be updated")
    }
}

The complete code for BaseViewController is provided below.

BaseViewController.swift

import UIKit  
class BaseViewController: UIViewController {  
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let baseTabBarVC = segue.destination as? UITabBarController {
            if let firstTab = baseTabBarVC.viewControllers?.first as? StartupQuizViewController {
                firstTab.delegate = self
            }
            if let secondTab = baseTabBarVC.viewControllers?[1] as? ScienceQuizViewController {
               secondTab.delegate = self
            }
        }
    }
}

extension BaseViewController: ScoreDelegate {  
    func addOneToScore() {
        print("score to be updated")
    }
}

Now if you run the app and tap on either Steve Jobs in the Startup Quiz tab or Thomas Edison in the Science Quiz tab, you can see a console log score to be updated :)

We have successfully made communication from the child container view to its parent view, however we still need to update the score in the ScoreViewController If you have noticed, you can see that the score always remains zero even though we tap on the correct answer. Lets go ahead and fix this now.

We have to first create a outlet for the score label in ScoreViewController

Select ScoreViewController in the document outline and then tap on Show the assistant editor. Select Automatic->ScoreViewController.swift

Control + drag from 0 label to ScoreViewController. Choose Outlet as connection and type scoreLbl as Name and then click on connect.

create outlet

Next we will initialize a score variable to zero and also create a method to increment it when the score needs to be updated and update the score label with the score. The Complete ScoreViewController.swift code is provided below.

ScoreViewController.swift

import UIKit

class ScoreViewController: UIViewController {  
    var score = 0

    @IBOutlet weak var scoreLbl: UILabel!

    func updateScore() {
        score = score + 1
        scoreLbl.text = String(score)
    }
}

Now we have the method ready to update the score in the ScoreViewController. We have to call this method from the BaseViewController to update the score.

Create a variable scoreVC to hold ScoreViewController reference. We will populate this variable in the override func prepare(for segue: UIStoryboardSegue, sender: Any?) method as we did earlier. Then we will use this variable to call the score update method in ScoreViewController. The updated BaseViewController.swift is provided below.

BaseViewController.swift

import UIKit  
class BaseViewController: UIViewController {  
    var scoreVC:ScoreViewController?
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let baseTabBarVC = segue.destination as? UITabBarController {
            if let firstTab = baseTabBarVC.viewControllers?.first as? StartupQuizViewController {
                firstTab.delegate = self
            }
            if let secondTab = baseTabBarVC.viewControllers?[1] as? ScienceQuizViewController {
                secondTab.delegate = self
            }
        }
        if let vc = segue.destination as? ScoreViewController {
            scoreVC = vc
        }
    }
}

extension BaseViewController: ScoreDelegate {  
    func addOneToScore() {
        print("score to be updated")
        scoreVC?.updateScore()
  }
}

Now when we run the app and tap on either Steve Jobs in the Startup Quiz tab or Thomas Edison in the Science Quiz tab, we can see that the score is updated.

The complete project is available in github for download.

Please post your feedback and queries in the comments section. Thank you.

comments powered by Disqus