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.
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
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
LikeLike
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)
}
LikeLike
Kindly refer to the updated code in Github
LikeLike
Hello, please give me an error on:
SegmentedControl.initUI ()
LikeLike
Kindly check the updated Swift 3.0 code on Github.
https://github.com/ahmed-abdurrahman/taby-segmented-control
LikeLike
It is working good,
Thanks.
LikeLike
Hello, please give me an error on:
SegmentedControl.initUI ()
I checked the updated Swift 3.0 code on Github
fatal error unexpectedly found nil while unwrapping an optional value
LikeLike
Hello, Thank you very much for the tutorial. Please, can you tell me how I change the color of the bar for a red
LikeLike
Hi Mauricio, the color of the bar will depend on the images you use for the selected and non selected backgrounds.
More here:
http://smnh.me/customizing-appearance-of-uisegmentedcontrol/
LikeLike
hello i have an error on this code
self.currentViewController!.view.removeFromSuperview()
self.currentViewController!.removeFromParentViewController()
LikeLike
how to swipe between pages and change the segment control index
LikeLike
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 }
LikeLike
Pingback: Music is Everything. Songs to the TikTok Videos | free-instagram.com
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.
LikeLike
Article was good, but if there are more tab in segment then it will not scroll,
Please update code for that.
LikeLike