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.
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.
Enter the Product Name, Organization Name and Organization Identifier. Choose Team as None, Language as Swift and Devices as Universal and tap on Next
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
Also change the class name from ViewController to BaseViewController in the source code
Tap on the View Controller in the storyboard, then tap on Identity inspector and type BaseViewController as the class name and press enter
Adding Container View
Lets move on to adding container view to the story board. Choose Container View from the object library.
Drag and drop the container view from the object library on to the storyboard.
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.
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
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.
Tap on the base view controller, then tap update frames in the bottom right corner to update the frames in accordance with the constraints.
After updating constraints, the storyboard will look like this.
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.
Type ScoreViewController in Class, UIViewController in Subclass, uncheck "Also create XIB file" and choose the language as Swift. Tap Next followed by Create
Now tap on the container view's view controller in the storyboard and choose identity inspector from the top right.
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.
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.
Tap on it and press delete.
Drag a Tab Bar Controller from the Object library on to the story board
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
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
The storyboard will look like this after adding constraints.
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.
Score View Design
Drag a label from the Object library to Score View Controller Scene and change its text to Score
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
Drag one more label to the right of the score label and set its text to 0.
Control + drag from zero to score and select horizontal spacing. You will get a few errors, which we will fix in the next step.
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
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.
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
Select Item 1 Tab bar item in the document outline, tap on Attributes Inspector and set the Title as Startup Quiz and press Enter
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
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
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.
Select Steve Jobs button and change the Top Space to: Equals constraint to 8 in the size inspector and press Enter
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.
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
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
Drag 2 buttons below the label one below the other. Change their titles to Thomas Edison and Albert Einstein
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
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.
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.
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.
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.