Using the Tab Bars in iOS

Container view controllers manage the content from multiple view controllers, and each have their own approach to view hierarchies. Certain container view controllers that you may encounter include

  • Tab bar controllers. Adds a tab bar at the bottom of the interface to navigate between view controllers.
  • Navigation controllers. Manages navigation between content view controllers.
  • Split view controllers. Shows two content view controllers simultaneously in certain devices and orientations and navigates between the two in other devices or orientations.

Tab Bar Controllers display icons at the bottom of the screen that represent different View Controllers. Tool Bar displays icons that represent different commands to manipulate data currently displayed on the screen.

Like a Navigation Controller, a Tab Bar Controller also groups related View Controllers together. The main difference is that a Navigation Controller can only display View Controllers in sequential order. On the other hand, a Tab Bar Controller can jump to another View Controller directly.

While a Navigation Controller always displays a navigation bar and a Back button on every View Controller it opens, a Tab Bar Controller always displays a list of icons at the bottom of the screen where each icon represents a different View Controller. By tapping on an icon, the user can jump to that particular View Controller right away. Then the user can return to the Tab Bar Controller again to see the list of icons representing the other View Controllers.

Switching View Controllers in a Tab Bar Controller

Tab Bar Controllers make it easy to switch from one view to another in any order you wish. Each icon on the Tab bar represents a different View Controller. The basic steps to using a Tab Bar Controller to represent different views are to add multiple View Controllers to your storyboard and then Ctrl-drag to connect the Tab Bar Controller to a View Controller.

Then the next step is to create a title and icon to represent that View Controller. You may also want to reorder the icons on the Tab bar or delete a View Controller and its Tab bar icon later.

To see how to create a simple Tab Bar Controller, follow these steps:

  1. Click the Main.storyboard file in the Navigator pane. Xcode displays the single view.
  2. Click the View Controller Scene at the top of the View Controller.
  3. Choose Editor > Embed In > Tab Bar Controller. Xcode displays a Tab Bar Controller in the storyboard connected to the existing View Controller.
  4. Click the Library icon to open the Object Library window.
  5. Drag and drop a View Controller from the Object Library window to the storyboard.
  6. Move the mouse over the Tab Bar Controller icon at the top of the Tab Bar Controller (or over the Tab Bar Controller in the Document Outline).
  7. Hold down the Control key and Ctrl-drag over the newly added View Controller you just added to the storyboard.
  8. Release the left mouse button and the Control key. A popup menu appears.
  9. Choose View Controllers under the Relationship Segue category. Xcode draws an arrow (segue) linking the Tab Bar Controller to the newly added View Controller.
  10. Click in the middle of the top View Controller (or click view in the Document Outline).
  11. Choose View > Inspectors > Show Attributes Inspector, or click the Attributes Inspector icon in the upper right corner of the Xcode window.
  12. Click in the Background popup menu and choose a color such as yellow.
  13. Click in the middle of the bottom View Controller (or click view in the Document Outline).
  14. Click in the Background popup menu and choose a color such as orange.

Sharing data between tabs

Notice that if you are one tab and then go to the other tab, your data seem to have disappeared. Several alternative solutions exist for sharing data between tabs, each with their own pros and cons:

  • Global variables
  • Singletons
  • Dependency injection

Global variables. If you create a variable outside of a class or struct, it’s automatically defined in the global scope, available from anywhere in your project. You could, for example, create a GlobalVars.swift file, that contains

var moviesManager = MoviesManager()

The movies view controllers would then automatically reference the moviesManager property in the global scope.

When the user navigates away from a tab, the related view controller will remain in memory. If the data is edited while the user is on a different tab, this change in data won’t be represented when the user returns. To ensure the table or collection is up to date when the user returns, you'll probably want to request them to reload in the viewDidAppear method.

There are several disadvantages of global variables: they create possible conflicts, make unit testing difficult, and make code more difficult to understand and harder to maintain.

Singletons. Singletons enforce that an object has been instantiated once and only once by main- taining its own instance internally.

It’s straightforward to convert a class to a singleton in Swift. All a class requires is a type property containing a reference to an instance of the class. You could make the singleton’s initializer private to ensure it can only be referenced from this instance property and not reinstantiated.

The following listing demonstrates how to convert MoviesManager to a singleton.

class MoviesManager {
    static let instance = MoviesManager() 
    private init() {}
    lazy var movies: [Movie] = self.loadMovies()
}

Inside your ViewController classes no longer instantiate a MoviesManager; rather, they access it via the instance property:

var moviesManager = MoviesManager.instance

Like the global variables solution, in this solution, view controllers won’t receive notification when the user updates data in another tab, and you'll want to reload the table or collection views in the viewDidAppear method.

There are several disadvantages of singletons: connections between different parts of your app can be obscured, making your app difficult to maintain and test.

Dependency injection. With dependency injection, an object can inject a dependency such as data or a service into another object.

Let’s use dependency injection to inject the moviesManager into the two scenes. Because the tab bar controller controls both scenes, it makes sense to instantiate the moviesManager in a subclass of the UITabBarController and then inject it into the scenes when required.

Remove the instantiation of the movies manager in the FirstViewController and SecondViewController classes, and replace with an implicitly unwrapped optional:

var moviesManager = MoviesManager!

Create a class that subclasses UITabBarController, and call it TabBarController.

Set TabBarController as the class of the tab bar controller in the Identity Inspector in the storyboard.

Instantiate the movies manager in your new class:

var moviesManager = MoviesManager()

To inject the movies manager into both view controllers, you’ll set up an Injectable protocol that both view controllers will adopt. Create the Injectable protocol in the TabBarController.swift file, with an inject method ready to pass in the movies manager.

protocol Injectable {
  func inject(data: MoviesManager)
}

In the table view controller, adopt the Injectable protocol and implement the inject method:

class FirstViewController: UITableViewController, Injectable { 
    func inject(data: MoviesManager) {
        self.moviesManager = data
    }
}    

After setting the instance variable, this is a good time to reload the table view. Use optional binding in case the tableView implicitly unwrapped optional hasn’t yet been instantiated.

Ensure the table view is up to date whenever the view appears:

override func viewDidAppear(_ animated: Bool) {
    tableView?.reloadData()
}

Repeat steps from above again, but for the other view controller. The view controllers are now ready to be injected!

Back in the TabBarController class, you’ll inject both view controllers with the data in the viewDidLoad method. UITabBarController has a viewControllers array that stores a reference to the navigation controllers containing the view controllers for each tab.

Loop through the viewControllers array, getting a reference to the navigation controllers, from which you can then get a reference to its root view controller to inject your data!

for navController in viewControllers! {
    if let navController = navController as? UINavigationController,
       let viewController = navController.viewControllers.first
            as? Injectable {
                viewController.inject(data: moviesManager)
    }
}

Customizing Tab Bar Icons

Tab Bar Controllers display icons on a Tab bar at the bottom of the screen. By default, this icon simply displays a generic label such as "Item", but you can customize this icon to include more descriptive text and/or an icon.

To see how to customize Tab bar icons, follow these steps:

  1. Download two or more free icons from any site that offers free icons.
  2. Click the Assets.xcassets folder.
  3. Click the + icon to display a popup menu
  4. Choose New Image Set. Xcode displays an Image item underneath AppIcon.
  5. Click this Image item and press ENTER to highlight the text.
  6. Type First and press ENTER.
  7. Drag and drop an icon into all the placeholders labeled 1x, 2x, and 3x.
  8. Repeat steps 4–7 but label this image set Second and drag and drop a different icon into each 1x, 2x, and 3x placeholder.
  9. Click the Main.storyboard file in the Navigator pane.
  10. Click Item in the Document Outline.
  11. Choose View > Inspectors > Show Attributes Inspector, or click the Attributes Inspector icon in the upper right corner of the Xcode window.
  12. Click the Image popup menu under the Bar Item category and select First.
  13. Click in the Title text field, delete the "Item" text, type the color of the View Controller (such as yellow), and press ENTER. Notice that the Tab bar now displays your chosen icon and text.
  14. Repeat steps 10–13 for the other Tab bar item except choose Second for its Image and type a different color for its Title.