Customize your UITabBarController

How to make your UITabBarController be more attractive?

Posted by Ky Nguyen on April 15, 2018

From 2017, design trending has changed to tab bar, instead of slide menu. UITabBarController has become one of the most popular controller. It’s very simple to use. But sometime, it’s too simple to customize and make attractive. Designers are artists, and they usually want tab bar like this

or like this

I had to implement a tab bar like above in 2015. My implementation at that time was bad. I used a UIViewController as a main controller, and added a custom view like a tab bar. And everytime a tab selected, embed the other UIViewController into the main controller. That’s not good option.

Today I make a UITabBarController with a custom view like a tab bar. With this solution, we can use the max strength of UITabBarController with the same behavior and code. Much easier and better than my implementation before. Hope anyone can easily customize to their design if they want after this note.

knTabBarItem

First thing need to be customized is the UITabBarItem. UITabBarItem is not flexible enough to be customized to anything I want. I need to use UIButton.

    class knTabBarItem: UIButton {
        // (1)
        var itemHeight: CGFloat = 0
        // (2)
        var lock = false
        // (3)
        var color: UIColor = UIColor.lightGray {
            didSet {
                guard lock == false else { return }
                iconImageView.change(color: color)
                textLabel.textColor = color
            }}
        
        // (4)
        private let iconImageView = knUIMaker.makeImageView(contentMode: .scaleAspectFit)
        private let textLabel = knUIMaker.makeLabel(font: UIFont.systemFont(ofSize: 11),
                                            color: .black, alignment: .center)
        
        convenience init(icon: UIImage, title: String,
                        font: UIFont = UIFont.systemFont(ofSize: 11)) {
            self.init()
            translatesAutoresizingMaskIntoConstraints = false
            iconImageView.image = icon
            textLabel.text = title
            textLabel.font = UIFont(name: font.fontName, size: 11)
            setupView()
        }
        
        // (5)
        private func setupView() {
            addSubviews(views: iconImageView, textLabel)
            iconImageView.top(toView: self, space: 4)
            iconImageView.centerX(toView: self)
            iconImageView.square()
            
            let iconBottomConstant: CGFloat = textLabel.text == "" ? -2 : -20
            iconImageView.bottom(toView: self, space: iconBottomConstant)
            
            textLabel.bottom(toView: self, space: -2)
            textLabel.centerX(toView: self)
        }
    }

(1): Some tab items can be bigger than others. I can easily set the height for them to make difference with others.

(2) (3): Same to (1), some tab items are very unacceptional with different color and don’t change color when selected.

(4): knUIMaker is my collection to make controls. Just easier to make UIButton, UIImageView, UILabel by code.

(5): addSubviews, top, centerX, bottom are from my knConstraints to make auto layout. I’m a fan of auto layout programmatically, so make controls and set layouts by code is what to do hundreds of times everyday.

knTabBar

Next thing I have to focus on is UITabBar.


    class knTabBar: UITabBar {
        // (1)
        var kn_items = [knTabBarItem]()
        convenience init(items: [knTabBarItem]) {
            self.init()
            kn_items = items
            translatesAutoresizingMaskIntoConstraints = false
            setupView()
        }
        
        override var tintColor: UIColor! {
            didSet {
                for item in kn_items {
                    item.color = tintColor
                }}}
        
        func setupView() {
            backgroundColor = .white
            if kn_items.count == 0 { return }
            
            // (2)
            let line = knUIMaker.makeLine(color: .gray, height: 0.5)
            addSubviews(views: line)
            line.horizontal(toView: self)
            line.top(toView: self)
            
            // (3)
            var horizontalConstraints = "H:|"
            let itemWidth: CGFloat = screenWidth / CGFloat(kn_items.count)
            for i in 0 ..< kn_items.count {
                let item = kn_items[i]
                addSubviews(views: item)
                if item.itemHeight == 0 {
                    item.vertical(toView: self)
                }
                else {
                    item.bottom(toView: self)
                    item.height(item.itemHeight)
                }
                item.width(itemWidth)
                horizontalConstraints += String(format: "[v%d]", i)
                if item.lock == false {
                    item.color = tintColor
                }
            }
            
            horizontalConstraints += "|"
            addConstraints(withFormat: horizontalConstraints, arrayOf: kn_items)
        }
    }

(1): I can’t override items in UITabBar, so I name it a little bit similar to easier to remember

(2): Add a line to separate the tab bar to the controller. Some designs need indicator at the selected item, I will add indicator here.

(3): Flexible to add items by programmatically. Thanks Apple for Auto Layout Programmatically.

knTabController

The easiest thing is here. Just inherit from UITabBarController, add some code, and it works.


    class knTabController: UITabBarController {
        var kn_tabBar: knTabBar!
        var selectedColor = UIColor.darkGray
        var normalColor = UIColor.lightGray {
            didSet {
                kn_tabBar.tintColor = normalColor
            }}
        
        private var kn_tabBarHeight: CGFloat = 49
        override func viewDidLoad() {
            super.viewDidLoad()
            tabBar.isHidden = true
            setupView()
        }

        func setupView() {}
        
        private func setTabBar(items: [knTabBarItem], height: CGFloat = 49) {
            guard items.count > 0 else { return }
            
            kn_tabBar = knTabBar(items: items)
            guard let bar = kn_tabBar else { return }
            kn_tabBar.tintColor = normalColor
            bar.kn_items.first?.color = selectedColor
            
            view.addSubviews(views: bar)
            bar.horizontal(toView: view)
            bar.bottom(toView: view)
            kn_tabBarHeight = height
            bar.height(kn_tabBarHeight)
            for i in 0 ..< items.count {
                items[i].tag = i
                items[i].addTarget(self, action: #selector(switchTab))
            }
        }
        
        @objc func switchTab(button: UIButton) {
            selectedIndex = button.tag
        }
    }

That’s ready for new tab bar. Just use it in a controller.

How to use?

  • Inherit class knTabController to your controller.
  • Override setupView method.

    class DoctorController: knTabController {
        override func setupView() {
            let home = knTabBarItem(icon: #imageLiteral(resourceName: "home"), title: "Home")
            let appointment = knTabBarItem(icon: #imageLiteral(resourceName: "appointment"), title: "Appointment")
            let add = knTabBarItem(icon: #imageLiteral(resourceName: "add"), title: "")
            add.lock = true
            add.itemHeight = 66
            let doctors = knTabBarItem(icon: #imageLiteral(resourceName: "doctors"), title: "Doctors")
            let porfolio = knTabBarItem(icon: #imageLiteral(resourceName: "user"), title: "Porfolio")

            let red = UIViewController()
            red.view.backgroundColor = .red
            let green = UIViewController()
            green.view.backgroundColor = .green
            let blue = UIViewController()
            blue.view.backgroundColor = .white
            let yellow = UIViewController()
            yellow.view.backgroundColor = .yellow
            let gray = UIViewController()
            gray.view.backgroundColor = .gray

            setTabBar(items: [home, appointment, add, doctors, porfolio])
            viewControllers = [red, green, blue, yellow, gray]
            normalColor = .red
        }
    }

  • Run and see. The main button (Hexagon Add) is locked, don’t change the color when selected.

But it’s better if we have animation, looks much better.

Add animation

  • In knTabController, change method switchTab content to

    let newIndex = button.tag
    changeTab(from: selectedIndex, to: newIndex)

  • Add method changeTab

    private func changeTab(from fromIndex: Int, to toIndex: Int) {
        kn_tabBar.kn_items[fromIndex].color = normalColor
        kn_tabBar.kn_items[toIndex].color = selectedColor
        animateSliding(from: fromIndex, to: toIndex)
    }

  • And result:

Conclusion

Hope this can help anyone want to customize a tab bar can do it effortless. Code is here.

I will add some more animations and some properties to make it more convenience in very near future. Suggestions and feedbacks are welcome.

Notes

I am a fan of Auto Layout Programmatically, so usually use my library, knConstraints for setting constraints. knConstraints is a very simple way to setup Auto Layout with very easy to read syntax. You can try it yourself here.