How boring is it when you discover on the latest update of your Zeplin’s project that you designer made a TabBar that doesn’t fit in 49px in height? Or when you discover that they only made an advanced design of TabBar that can’t be a subclass of UITabBar? We all know that moment when you have to imagine a custom hierarchy for your app just for a designer… 😜
TabBarController acts like a UITabBarController that allows you to customize TabBar, transforms and animations. You can even set a custom anchor for your TabBar. You want a top tabBar? Or just a bottom TabBar on tvOS? Well… you can easily do all these things with exactly 0 line of code, directly from your storyboard (or programmatically, if you’re not a big fan of storyboards 😉)
Xcode 9.0 Swift 4.0
To run the example project, clone the repo, and run pod install
from the Example directory first.
TabBarController is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'TabBarController'
You can set up a TabBarController directly from your storyboard, to do it :
- Add a UIViewController on your storyboard and make it inherits from TabBarController
- Change the TabBarController's storyboardSeguesCount attribute
- Add custom segues that inherit from TabBarSegue
- For each of your segues you have to set an identifier that starts with 'tab' and ends with its index in TabBar
Example: if you want 4 viewControllers in your tab, you have to set storyboardSeguesCount to 4, and name your custom segues tab0, tab1, tab2 and tab3
Et Voila !
let tabBarController = TabBarController(viewControllers: [...])
self.present(tabBarController, animated: true, completion: nil) // present it, set it as rootViewController, what ever..
self.hidesBottomBarWhenPushed = true // automatically hide tabBar when pushed
self.tab.controller?.setTabBarHidden(true, animated: true) // manually hide tabBar, animated or not
See iOS9ViewController.swift example
If you want to handle the scroll to top functionallity like system's UITabBarController when you tap on selected tab in TabBar
func tabBarAction() {
//
self.tableView.setContentOffset(.zero, animated: true)
}
See iOS9TableViewController.swift example
TabBarController provide extensions for UIViewController:
self.tab.controller // return the tabBarController of self
self.tab.bar // return the tabBar of self.tab.controller
self.tab.barItem // return the tabBarItem of self's parent in TabBarController
self.tab.navigationController /* In case of the root view controller of your controller's tab is a UINavigationController, TabBarController add it in private parent controller.
Using self.tab.navigationController on this private parent controller (via self.tab.controller?.viewControllers for example) return your navigationController*/
self.tab.isNavigationController // return true if self.tab.navigationController != nil
TabBarController allows you to use UINavigationController as root view controller of each tab of your tabBarController, But to do so, it add it in a private parent controller and set its delegate to the TabBarController If you want to use custom delegate for your UINavigationController please redirect thoses events to the tabBarController, otherwise hidesBottomBarWhenPushed and other displays functionalities will not work.
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
//
navigationController.tab.controller?.navigationController(navigationController, willShow: viewController, animated: animated)
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
//
navigationController.tab.controller?.navigationController(navigationController, didShow: viewController, animated: animated)
}
Create a UIView class and make it inherits from TabBarProtocol
import TabBarController
class YourTabBar: UIView, TabBarProtocol {
weak var delegate: TabBarDelegate?
func setItems(_ items: [UITabBarItem]?, animated: Bool) {
// Update view
}
func setSelectedItem(_ item: UITabBarItem?, animated: Bool) {
// Update view
}
}
See SpringboardTabBar.swift example
Link the tabBar outlet from your TabBarController to your custom tabBar in storyboard
let tabBar = YourTabBar()
let tabBarController = TabBarController(viewControllers: [...], tabBar: tabBar)
TabBarController supports four anchors for TabBar:
- top: tvOS style
- bottom: iOS style
- left
- right
Set the tabBarAnchorIndex attribute of your TabBarController (0: top, 1: bottom, 2: left, 3: right)
let tabBar = YourTabBar()
let anchor: TabBarAnchor = .top
let tabBarController = TabBarController(viewControllers: [...], tabBar: tabBar, anchor: anchor)
class YourTabBar: UIView, TabBarProtocol {
var additionalInset: CGFloat { return 0 } // positive or negative value
func setAnchor(_ anchor: TabBarAnchor) {
// Update view
}
func setTabBarHidden(_ hidden: Bool) {
// Update view
}
}
TabBarProtocol provides optionnal method that allows you to customize animations and frames for your TabBar.
class YourTabBar: UIView, TabBarProtocol {
func animator() -> TabBarAnimator? {
return YourTabBarAnimator()
}
}
class YourTabBarAnimator: TabBarAnimator {
func tabBarInsets(withContext context: TabBarAnimatorContext) -> UIEdgeInsets {
// return additional insets below your TabBar
}
func animateTabBar(using context: TabBarAnimatorContext) {
// Animate and update frame of the TabBar
UIView.animate(duration: 0.3, animations: {
context.tabBar.frame = finalFrame // update frame
context.animate() // animate insets updates
}, completion: context.completeTransition) // call completeTransition
}
}
See TabBarAnimator.swift
If you want to use custom animations on your TabBarController you have to make it inherits from TabBarControllerDelegate
extension YourTabBarController: TabBarControllerDelegate {
func tabBarController(_ tabBarController: TabBarController, animationControllerFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
// return your custom UIViewControllerAnimatedTransitioning
}
}
See SpringboardTabBarController.swift example
TabBarController is optimized for iOS 11 and safeArea, if you want support iOS 9 & 10 you need to use additional insets. This library provides different ways to do so.
The TabBarChildControllerProtocol provides two optional properties that allows you to manage TabBar insets easily :
var tabBarTopInsetConstraint: NSLayoutConstraint!
var tabBarBottomInsetConstraint: NSLayoutConstraint!
var tabBarLeadingInsetConstraint: NSLayoutConstraint!
var tabBarTrailingInsetConstraint: NSLayoutConstraint!
Since UIViewController inherits from TabBarChildControllerProtocol, just add these properties in your class (use IBOutlet if you want to use them Interface Builder)
See iOS9ViewController.swift example
If you want to add insets on your UIScrollView instead of directly update its frame, you can handle it using this method:
func updateTabBarInsets(_ insets: UIEdgeInsets) {
self.tableView.contentInset.bottom = insets.bottom
self.tableView.scrollIndicatorInsets.bottom = insets.bottom
}
See iOS9TableViewController.swift example
Arnaud Dorgans, arnaud.dorgans@gmail.com
TabBarController is available under the MIT license. See the LICENSE file for more info.