Creating a UITabBarController Framework
Navigation is one of, if not, the most important aspect of any application. Choosing how users will navigate through the different screens plays a vital role in your applications success.
With the dawn of the AppStore it was very clear what was the most favoured means of navigation. Hamburger menus, Sliding menus, Sidebars whatever you would like to call them. This form of navigation became synonymous with apps. Personally I find many flaws with Sidebars. But since I am not a UX designer, you can read more design-oriented reviews in this article.
As you might have deduced from the title of this post my favourite form of app navigation is a UITabBar. If we look at most of the applications from our beloved Apple the use of a UITabBar is prevalent in most of them. App Store, Watch, Clock and Music — all use UITabBar for navigation. To read more about the UX benefits of using it check this post.
Why create a Framework?
UITabBar is very simple to implement using UIKit in Swift already. So what’s the need of creating an entire framework? The standard UITabBar is a bit boring for me, I wanted to add more flair🎉 to the component. In addition, it’s good to create something reusable, which can be styled and animated without changing much of the code.
Now that we have discussed why to make a framework and set out what we want to achieve, let’s get to CODE 😎! Just a few principles and concepts we want our framework to adhere to.
Principles & Concepts
- Protocol Oriented
As a precursor I would advise that you be familiar with the following principles:
- Access modifiers
- DataSource & Delegation
- Programmatic constraints
These are the core principles we should keep in mind when designing and developing our framework’s architecture. I will expand more on each concept as we come across it in the code.
At this point you have created your framework project given it a cool name like MDVTabBarController. To start off, some group structure 👷♀️.
A TabBarController is basically a combination of three types of components. They are: TabBarItems which represent each tab, a TabBar which holds the items and lastly a TabBarController which as the name suggests controls the previously mentioned components. We will start with the smallest component and build our framework from there. At the end of this, we want to have a framework to make use of plugin animations.
Create a file named MDVTabBarItemable, place it in the Protocol group, then create a file named MDVTabBarItem and place it in the MDVTabBar group.
MDVTabBarItemable is the protocol we want all our TabBarItems to conform to. We will be replacing the standard UITabBar and UITabBarItem with a UIView as this I believe will give more flexibility. The protocol defines two methods configure which is used to setup our TabBarItem and setState. The last one will set the TabBarItem to selected or unselected. The typealias MDVTabBarContainer defines an interface which our animators are to use.
MDVTabBarItem takes a UITabBar item with an image and creates a UIView with the image in the centre. For now MDVTabBarItem will not handle title labels but this can easily be added by creating a UILabel and placing it in the UIView. You will notice that required init has not been implemented. This is the initialiser used by storyboards to initialise components. We will not be making use of storyboards so no need to implement it.
The MDVTabBar is quite an involved class so we will look at it in sections. Firstly let’s look at the properties and initialisation. Create a file named MDVTabBar and place it in the MDVTabBar group.
MDVTaBarDelegate and MDVTabBarDataSource are protocols we will define later. When creating a reusable framework always keep in mind accessibility modifiers. Start by having your variables as private as possible. If you need a variable to be modified outside of its defining class, create a public setter method inside the class to handle modification. In Swift private(set) gives us the ability to keep our property private and at the same time have an internal scope getter.
The animator has already been introduced, so let us dive into the implementation. Create a file named MDVTabBarAnimatable place it inside the Protocol group.
We have two methods:
- prepareForAnimation — This method is called upon initialisation of the TabBar. It puts in place all the necessary components for whatever style or animation you want for your TabBar.
- performAnimation — This is called when the user taps on a button on the TabBar. It performs the animation from one index to the next.
This protocol defines an interface that all animators should conform to. Anyone can create an animator simple by implementing this protocol accordingly. That means our framework can be easily extended to support more animations without any changes needed to the code.
In object-oriented programming, the open/closed principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”; that is, such an entity can allow its behaviour to be extended without modifying its source code. (reference)
Create a file name MDVTabBarDataSource+Delegate and place it inside the Protocol group.
We will now go back to the MDVTabBar and complete the implementation of the class. Starting with the delegate and datasource didSet methods.
Whenever our DataSource is set, we want to configure. This may be due to animator change or if the tabBarItems have changed for some reason. We also want to set our icon state to the initial state whenever our delegate is set. This will put our TabBar in the right state and select the initial index. Now let’s implement the missing methods for MDVTabBar.
- configure — We make sure that our DataSource is not nil, which should be impossible in this instance. Then we set our tabBarItems and animator from the provided datasource.
- createContainerRects — This method is responsible for creating a frame for each tabBarItem.
- createTabBarItemContainerRect — The frame that were created in the createContainerRects are then passed to this method to create a UIView which will serve the purpose of a container/holder view for our tabBarItem.
- createTabBarItems — Next we create MDVTabBarItems using the aforementioned containerRects.
- touchUpInsideForTabBarButton — Now we need a touch event for each tabBarButton. All tabBarButtons call the same method but we use it’s tag to identify which button is being tapped.
- changeIconState — Finally we need a way to toggle between the selected and default state of our icon.
All these methods work together to create a UITabBarItem, position it and give it a selector. Each method tries at best to only perform one unit of work.
The single responsibility principle is a computer programming principle that states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.(reference)
This class is quite involved as well so we will look at its creation in parts. So, create a file name MDVTabBarController and place it in the MDVTabBar group.
I would like to believe there is nothing out of the ordinary here apart from the DataSource and Delegate which we are yet to define. Take note of the fact that the class is declared open.
We make use of only two methods to create our tabBarController.
- configureViews — We unwrap the dataSource. Hide the standard tabBar as we will be placing ours in it’s place. Set our viewControllers from the dataSource. Initialise the MDVTabBar and set it up using constraints. Finally we set the tabBarController to be the delegate and datasource of the MDVTabbar.
- setupTabBarConstraints — We use constraints to layout our tabBar.
Now go back to the mdvTabBarControllerDataSource and implement the didSet methods like so:
Implementing MDVTabBarDelegate & MDVTabBarDataSource
Create a file name MDVTabBarControllerDataSource+Delegate and place it inside the Protocol group.
These are the methods that we want to expose as API methods for anyone to implement our framework. Since MDVTabBarController is open it can be subclassed. That class will serve as the delegate and datasource, implement these methods. Let’s breakdown how these methods are to be used.
- tabBarControllerInitialIndex — Used to set the initial index for the tabBar.
- tabBarControllerViewControllers — Provides the viewControllers for the tabBar.
- tabBarHeight — Provides the desired height for the tabBar. Yes! We can make our tabBar smaller or taller than the standard UIKit one 😝. Don’t go crazy, always follow Apple’s HIG.
- tabBarBackgroundColor — Used to set the background colour of the tabBar
- tabBarAnimator — Provides the Animator to use for animations
- didSelectIndex — This is a delegate method that is called when a tabBarItem is selected. This method can be utilised to perform custom actions when tabs are selected. For example open a share-sheet or on a view that uses a camera trigger permissions.
In the previous methods number 2. makes use of MDVTabbableViewController which is a class we have not defined as yet. This class definitely wins the award for most creative name 😅. Create a file named MDVTabbableViewController and place it in the Protocol group. This is our final protocol and class.
This combination of protocol and typealias is used as a bodyguard for our TabBarControllerDataSource. This will ensure any viewController to be displayed on our tabBarController should provide a tabBarItem or it won’t compile.
Finally we have completed our framework! All that is left to do is create a project we will use it in and implement a cool animation. To not prolong this post I will provide a link to the Github with an example of how the framework can be used.
We have built a tabBarController that has plug and play animation and styling . I would not say the framework is complete. The project is a long way from being a complete solution for tabBarController libraries already out like ESTabBarController or RAMAnimatedTabBarController.
I encourage you to check out the repo, maybe fork it and create your own animation 😀. It would be interesting to see what some of you could come up with. In the following weeks I’ll be posting more examples of animators like the one in the beginning of this post and how to achieve them.
Thanks you for reading and I look forward to your feedback and contributions to this framework.