TabBarController 1.0.1

TabBarController 1.0.1

Maintained by Arnaud Dorgans.



TabBarController

CI Status Version License Platform

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 😉)

Requirements

Xcode 9.0 Swift 4.0

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

Installation

TabBarController is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'TabBarController'

Usage

Storyboard

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 !

Programmatically

let tabBarController = TabBarController(viewControllers: [...])
self.present(tabBarController, animated: true, completion: nil) // present it, set it as rootViewController, what ever..

Hide TabBar

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

Scroll To Top

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

Tab

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

NavigationController

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)
}

Customization

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

Storyboard

Link the tabBar outlet from your TabBarController to your custom tabBar in storyboard

Programmatically

let tabBar = YourTabBar()
let tabBarController = TabBarController(viewControllers: [...], tabBar: tabBar)

Advanced Customization

Anchor

TabBarController supports four anchors for TabBar:

  • top: tvOS style
  • bottom: iOS style
  • left
  • right

Storyboard

Set the tabBarAnchorIndex attribute of your TabBarController (0: top, 1: bottom, 2: left, 3: right)

Programmatically

let tabBar = YourTabBar()
let anchor: TabBarAnchor = .top
let tabBarController = TabBarController(viewControllers: [...], tabBar: tabBar, anchor: anchor)

TabBar

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
    }
}

Animator

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

Transition

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

Prior iOS 11 support

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.

TabBar Top/Bottom Inset Constraint

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

Update TabBar Insets

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

Author

Arnaud Dorgans, [email protected]

License

TabBarController is available under the MIT license. See the LICENSE file for more info.