How to Switch View Controllers using Segmented Control – Swift 3.0

I received this question many times, “How to build Android-like tab bar in iOS?”
Find the answer below:

One of the typical usages of a UISegmentedControl is to use it like a tab view controller, where you want to switch between different view controllers. This post will demonstrate how to achieve this functionality in Swift through the UIViewController containment API, that allows embedding your UIViewController inside another. Also how to style your segmented control to look like a typical tab bar.While working on Zingoo, we had a requirement to switch between the photos and the info tabs through an Android-like tab bar. This was the target UI to achieve.

phototab

To achieve this, we had to have 3 different UIViewControllers, the parent (containing) view controller, and a view controller for each tab. We designed the 3 view controllers normally in the storyboard. The containing view controller will have a view that will contain the other view controllers views. We’ll call it “contentView”.

Let’s dive into code

We’ll need 2 IBOutlets, contentView and our segmented control.


@IBOutlet weak var segmentedControl: TabySegmentedControl!
@IBOutlet weak var contentView: UIView!

Did you hear before about self.storyboard?

The first function we need, is a function that instantiates -or returns a cached instance- for the current selected tab index. We’ll make use of the self.storyboard to instantiate a view controller from our storyboard given the identifier.


var currentViewController: UIViewController?
lazy var firstChildTabVC: UIViewController? = {
let firstChildTabVC = self.storyboard?.instantiateViewController(withIdentifier: "FirstViewControllerId")
return firstChildTabVC
}()
lazy var secondChildTabVC : UIViewController? = {
let secondChildTabVC = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewControllerId")
return secondChildTabVC
}()
func viewControllerForSelectedSegmentIndex(_ index: Int) -> UIViewController? {
var vc: UIViewController?
switch index {
case TabIndex.firstChildTab.rawValue :
vc = firstChildTabVC
case TabIndex.secondChildTab.rawValue :
vc = secondChildTabVC
default:
return nil
}
return vc
}

Containment API

From the documentation “A custom UIViewController subclass can also act as a container view controller. A container view controller manages the presentation of content of other view controllers it owns, also known as its child view controllers. A child’€™s view can be presented as-is or in conjunction with views owned by the container view controller.”

The second function, is the function responsible for establishing a parent-child relation between the containing view controller and the current child view controller to be displayed.


func displayCurrentTab(_ tabIndex: Int){
if let vc = viewControllerForSelectedSegmentIndex(tabIndex) {
self.addChildViewController(vc)
vc.didMove(toParentViewController: self)
vc.view.frame = self.contentView.bounds
self.contentView.addSubview(vc.view)
self.currentViewController = vc
}
}

Note, your container view controller must associate a child view controller with itself before adding the child’€™s root view to the view hierarchy.
Now, we can start using this function in our viewDidLoad function to display the default tab.


override func viewDidLoad() {
super.viewDidLoad()
segmentedControl.initUI()
segmentedControl.selectedSegmentIndex = TabIndex.firstChildTab.rawValue
displayCurrentTab(TabIndex.firstChildTab.rawValue)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let currentViewController = currentViewController {
currentViewController.viewWillDisappear(animated)
}
}

Switching Tabs

Now, switching tabs is just a matter of removing the currentViewController and its view from the parent view controller, and then call our displayCurrentTab function.


@IBAction func switchTabs(_ sender: UISegmentedControl) {
self.currentViewController!.view.removeFromSuperview()
self.currentViewController!.removeFromParentViewController()
displayCurrentTab(sender.selectedSegmentIndex)
}

TabBar Look and Feel

Now, it’s time for styling our UISegmetedControl to look like a tab bar.
Generally speaking, the appearance of the UISegmentedControl could be customized by using the following methods that set the background and divider images:


func setBackgroundImage(backgroundImage: UIImage?, forState state: UIControlState, barMetrics: UIBarMetrics)
func setDividerImage(dividerImage: UIImage?, forLeftSegmentState leftState: UIControlState, rightSegmentState rightState: UIControlState, barMetrics: UIBarMetrics)

You’ll find more details on this topic on smnh.me, an excellent post really.

Final View Controller

Here’s how it looks on my demo app:

Here’s the final version of our container view controller.

Also, this is a ready demo app, check it: https://github.com/ahmed-abdurrahman/taby-segmented-control


class ParentViewController: UIViewController {
enum TabIndex : Int {
case firstChildTab = 0
case secondChildTab = 1
}
@IBOutlet weak var segmentedControl: TabySegmentedControl!
@IBOutlet weak var contentView: UIView!
var currentViewController: UIViewController?
lazy var firstChildTabVC: UIViewController? = {
let firstChildTabVC = self.storyboard?.instantiateViewController(withIdentifier: "FirstViewControllerId")
return firstChildTabVC
}()
lazy var secondChildTabVC : UIViewController? = {
let secondChildTabVC = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewControllerId")
return secondChildTabVC
}()
// MARK: – View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
segmentedControl.initUI()
segmentedControl.selectedSegmentIndex = TabIndex.firstChildTab.rawValue
displayCurrentTab(TabIndex.firstChildTab.rawValue)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let currentViewController = currentViewController {
currentViewController.viewWillDisappear(animated)
}
}
// MARK: – Switching Tabs Functions
@IBAction func switchTabs(_ sender: UISegmentedControl) {
self.currentViewController!.view.removeFromSuperview()
self.currentViewController!.removeFromParentViewController()
displayCurrentTab(sender.selectedSegmentIndex)
}
func displayCurrentTab(_ tabIndex: Int){
if let vc = viewControllerForSelectedSegmentIndex(tabIndex) {
self.addChildViewController(vc)
vc.didMove(toParentViewController: self)
vc.view.frame = self.contentView.bounds
self.contentView.addSubview(vc.view)
self.currentViewController = vc
}
}
func viewControllerForSelectedSegmentIndex(_ index: Int) -> UIViewController? {
var vc: UIViewController?
switch index {
case TabIndex.firstChildTab.rawValue :
vc = firstChildTabVC
case TabIndex.secondChildTab.rawValue :
vc = secondChildTabVC
default:
return nil
}
return vc
}
}

15 thoughts on “How to Switch View Controllers using Segmented Control – Swift 3.0

  1. Hi , I use your code for the segmented controll and it works perfectly . I have a question how to use the UIPagerViewControll to have the swipe effect when we change from one view controller to an other viewcontroller . Regards

    Elikia

    Like

  2. I am getting error in the first line of this function that is
    @IBAction func switchTabs(sender: UISegmentedControl) {
    self.currentViewController!.view.removeFromSuperview() –> fatal error: unexpectedly found nil while unwrapping an Optional value
    self.currentViewController!.removeFromParentViewController()
    displayCurrentTab(sender.selectedSegmentIndex)
    }

    Like

  3. hello i have an error on this code
    self.currentViewController!.view.removeFromSuperview()
    self.currentViewController!.removeFromParentViewController()

    Like

  4. Nice article! Inside the method viewControllerForSelectedSegmentIndex(_:), to avoid the default case in switch clause, why not to use the guard clause and initialize the enum?

    guard let tabIndex = TabIndex(rawValue: index) else { return nil }

    Like

  5. Pingback: Music is Everything. Songs to the TikTok Videos | free-instagram.com

  6. Thanks for the code. Note that you need to add an observer to contentView.bounds to catch resize events and set the bounds of the dynamic child view. Otherwise it will not resize when the device is located or other autosizing UI changes take place.

    Like

Leave a comment