Skip to content

Learn

How to use UITabBarController with Swift

Learn how to build and customize Swift UITabBarController layouts, then validate tab navigation, UI flows, and releases at scale with Tricentis Testim Mobile.

UITabBarContoller

For Swift developers looking to deepen their iOS expertise, mastering Apple’s core UI navigation components is essential. One of the most widely used—and instantly recognizable—navigation patterns in iOS apps is the UITabBarController. From productivity tools to social and finance apps, tab-based navigation provides a clean, intuitive way to organize multiple views within a single application.

In this article, you’ll learn how to implement a basic UITabBarController in Swift by building a simple app that displays the current time, local weather information, and your location on a map. This walkthrough is designed for Swift developers who want hands-on experience with UIKit navigation patterns.

As apps grow in complexity, ensuring that UI navigation and interactions work reliably across devices becomes increasingly important. This is where strong testing practices—and platforms like Tricentis Testim Mobile—play a critical role, helping teams validate UI behavior and user flows at scale.

UITabBarController basics

A UITabBarController, like all other UIController-based UI elements in an iOS developer’s toolset, is known as a navigation interface, or as Apple puts it: “A container view controller that manages a multiselection interface, where the selections determine which child view controller to display.”

A UITabBarController, like all other UIController-based UI elements in an iOS developer’s toolset, is known as a navigation interface, or as Apple puts it: “A container view controller that manages a multiselection interface, where the selections determine which child view controller to display.”

What is a TabController?

What this means is that, just like a hierarchy, the UITabBarController functions as a parent container handling the complexities of navigation for you while displaying only the child view the user is presently interacting with, offering a straightforward interface.

One screen is visible at a time, keeping the UI simple and more approachable, while the rest of the views are accessible at the touch of the tab buttons on the bottom of the screen.

Now, unless you’ve been living under a rock, you’ve seen these before. Here’s an example to ensure we’re all on the same page.

tab controller

Simple, elegant, great.

Let’s build it.

Creating a UITabBarController

Open XCode and create a new iOS app. Select Storyboard as your design framework of choice, and move into the Main storyboard.

UITabBarController

Note: If you want to know how to create great apps using SwiftUI, you should check out my other articles on the topic here.

Great! Now, you need to add the UITabBarController as the default controller, since the one that XCode starts you with is just a standard UIViewController.

Delete the original view controller and click on the plus (+) button on the top right of your developer tool. Then, search for the tab bar controller and simply drag it into the storyboard.

UITabBarController story board

And there you go. You have everything you need for your new and fancy tab bar app. Well, mostly…

If you want to be adventurous and run your app, you’ll see the following:

run app

Now, what’s going on here? First, let’s check the console for some clues.

console

All right, that’s helpful. This basically means that the app doesn’t have a view controller set as the starting point—a simple fix.

Click on the parent controller, and then, in the settings tab on the right side corner, tick the Is Initial View Controller box.

initial view control box

Now, rerun the code and…

re run the code

All right. Now we’re talking!

Working on your UITabBarController

So far, you’ve mostly been playing around with the default features and setup XCode offers. No code has been written. But don’t worry, to actually use the power of the tab bar controller, you’ll be programming some views in it that are actually more useful than a blank screen.

Adding Some Views

The first view you’ll add to your tab bar controller app is a simple clock.

To simulate a clock, all you need is a single label that’s constantly updated with the device’s time every second. So, start by dropping a label and setting the view constraints and connections.

add some views

Now, create some Swift classes with their class definitions. You can call them item1.swift, item2.swift, and item3.swift on your project. Following that, go to the respective view on the storyboard and set the class of the view controller to be the class of item1.

Once you’ve done that, go to the class and add the following code:

//
//  ViewController.swift
//  TabBarControllerSample
//
//  Created by Juan Mueller on 11/19/22.
//  For more, visit www.ajourneyforwisdom.com

import UIKit

class ViewController: UIViewController {
    // Outlet for label
    @IBOutlet weak var clockLabel: UILabel!
    // Date formatter
    let dateFormatter = DateFormatter()

    override func viewDidLoad() {
        // Call the viewdidload super
        super.viewDidLoad()
        // Configure date formatter
        dateFormatter.dateStyle = .none
        dateFormatter.timeStyle = .medium
        // Schedule a timer to trigger every second to invoke method updateLabel
        Timer.scheduledTimer(timeInterval: 1,
                             target: self,
                             selector: #selector(updateLabel),
                             userInfo: nil,
                             repeats:true);
    }

    // Method that updates the label text witht he current date time
    @objc func updateLabel() -> Void {
        // Update the text of the clock label with the time of the current date
        clockLabel.text = dateFormatter.string(from: Date());
    }
}

Finally, set the label referencing outlet to the label defined in the Swift class you created. Now, run your code, and you should see a clock running on the item1 tab.

clock item1

Adding more views

For the second tab, let’s add a new controller: a table view controller. This controller will display some weather information on a table structure. Nothing complicated. Start by dropping a TableViewController from the items, just as you did with the TabBarController. Then, with the control button pressed, drag a connection between the TabBarController and the TableViewController, and set the relationship as a child view reference.

That’s how you add more views to your tab bar.

Following that, modify the code on your item2.swift class to the following:

//
//  ViewController2.swift
//  TabBarControllerSample
//
//  Created by Juan Mueller on 11/19/22.
//  For more, visit www.ajourneyforwisdom.com

import Foundation
import UIKit

class ViewController2: UIViewController, UITableViewDataSource, UITableViewDelegate {
    // Outlet for tableView
    @IBOutlet weak var tableView: UITableView!
    // Weather data JSON property
    var wdata: [String: Any]?

    override func viewDidLoad() {
        // Call the viewdidload super
        super.viewDidLoad()
        // Always register the tableview cell with the corresponding identifier in the storyboard
        // so it can be reused
        tableView?.register(UITableViewCell.self, forCellReuseIdentifier: "dataCell")
        // Set the tableview datasource to self
        tableView?.dataSource = self
        // invoke the requestWeatherData method and handle its completion
        requestWeatherData {
            // Code inside this block will be executed in the main thread
            DispatchQueue.main.async { [self] in
                // Reload the tableview
                tableView?.reloadData()
            }
        }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // Retrieve the registered reusable cell from the tableview
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "dataCell",
                                                                  for: indexPath)
        // Switch between the 4 possible rows to display
        switch indexPath.item {
        case 0:
            // Set the cell text
            cell.textLabel?.text = "Temp: " + (wdata != nil ? "\((wdata!["current_weather"] as! [String : Any])["temperature"] ?? "---")" : "---")
            break
        case 1:
            // Set the cell text
            cell.textLabel?.text = "Elevation: " + (wdata != nil ? "\(wdata!["elevation"] ?? "---")" : "---")
            break
        case 2:
            // Set the cell text
            cell.textLabel?.text = "Wind speed: " + (wdata != nil ? "\((wdata!["current_weather"] as! [String : Any])["windspeed"] ?? "---")" : "---")
            break
        case 3:
            // Set the cell text
            cell.textLabel?.text = "Feels like: " + (wdata != nil ? "\(((wdata!["daily"] as! [String : Any])["apparent_temperature_max"] as! [NSNumber])[0])" : "---")
            break
        default:
            break
        }
        // Return cell
        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        // Set the height of cells as fixed
        return 60.0
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // Set the number of rows to 4
        return 4
    }

    // Method that requests the weather data to meteo and updates the wdata property
    func requestWeatherData(_ completion: @escaping () -> Void) {
        // create the url
        let url = URL(string: "https://api.open-meteo.com/v1/forecast?latitude=32.22&longitude=-110.93&daily=temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min,sunrise,sunset¤t_weather=true&temperature_unit=fahrenheit&timezone=America%2FLos_Angeles")!

        // now create the URLRequest object using the url object
        let request = URLRequest(url: url,
                                 cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
                                 timeoutInterval: 30.0)

        // create dataTask using the session object to send data to the server
        let task = URLSession.shared.dataTask(with: request, completionHandler: { [self] data, response, error in

           guard error == nil else {
               return
           }

           guard let data = data else {
               return
           }

          do {
             //create json object from data
             if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
                 // Update wdata property
                 wdata = json
                 // Call completion handler
                 completion()
             }
          } catch let error {
            print(error.localizedDescription)
          }
        })
        // Trigger request
        task.resume()
    }
}

I know it’s a lot of code, but don’t let it intimidate you. Most of it is standard code used on all table view designs, and it’s pretty straightforward.

For now, focus on the @IBOutlet.

Like last time, you need to connect the view from the storyboard to the actual class. You can do this similarly by dragging and dropping with the control button pressed.

Now, rerun your code, and it should show you the weather information like the following:

weather information

Now, for the final view, let’s drop a MapView on the last view controller associated with the tab bar and expand it to fit the view edges. Again, don’t forget to set the constraints to match the edges. Otherwise, it won’t keep the size, depending on the device.

Put the following code on the item3.swift class and connect the @IBOutlet accordingly.

//
//  ViewController3.swift
//  TabBarControllerSample
//
//  Created by Juan Mueller on 11/19/22.
//  For more, visit /www.ajourneyforwisdom.com

import Foundation
import UIKit
import MapKit
import CoreLocation

class ViewController3: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
    // Outlet for mapView
    @IBOutlet weak var mapView: MKMapView!
    // Location manager property
    var locationManager: CLLocationManager?

    override func viewDidLoad() {
        // Call the viewdidload super
        super.viewDidLoad()
        // Initialize location manager
        locationManager = CLLocationManager()
        // Configure location manager to request for location permission
        locationManager?.delegate = self
        locationManager?.requestAlwaysAuthorization()
        locationManager?.requestWhenInUseAuthorization()
        locationManager?.desiredAccuracy = kCLLocationAccuracyBest
        // Configure map view
        mapView.delegate = self
        mapView.mapType = .standard
        mapView.isZoomEnabled = true
        mapView.isScrollEnabled = true
    }

    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        // Check if permissions are not available
        if manager.authorizationStatus == .denied ||
            manager.authorizationStatus == .restricted ||
            manager.authorizationStatus == .notDetermined {
            return
        }
        // Trigger location retrieval
        locationManager?.startUpdatingLocation()
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations
                         locations: [CLLocation]) {
        // Initialize coordinate with user location data
        let locValue:CLLocationCoordinate2D = manager.location!.coordinate
        // initialize default map span and region
        let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
        let region = MKCoordinateRegion(center: locValue, span: span)
        // Set map region to frame the mapView on a specific region
        mapView.setRegion(region, animated: true)
        // Initialize map annotation (pin)
        let annotation = MKPointAnnotation()
        annotation.coordinate = locValue
        annotation.title = "You are Here"
        // Add annotation on map view
        mapView.addAnnotation(annotation)
    }
}

Here, you’ll also need to set the permissions to your current location on the info.plist file so that the device requests them on load.

Now rerun your app, and that’s it. You got an interactive map centered on your location!

Customizing the TabBarController

As you’ve seen, working with a TabBarController is pretty straightforward. Adding your views is pretty much a drag-and-drop affair, and ensuring you have your outlets adequately set is easy. And if you want to make further customizations to the tab bar section, well, that’s pretty simple too.

First, to change the text and icon for the pages, you can do so on the corresponding view controller settings.

tab bar control settings

Secondly, to change the style and format of the tab bar, you can modify the settings in the TabBarController bar itself. For example, you can set the background, font, tint, style, and, most importantly, appearance.

This last setting controls whether the tab bar lives on top of your content (Scroll Edge) or below it (Standard). If it lives on top, the content will be partially visible underneath with transparency when you scroll. However, you can change this, if you prefer.

tab bar controller

Customizing the tab bar

Customizing the tab bar is straightforward:

  • Change tab titles and icons in each view controller’s settings
  • Adjust appearance (background, tint, style) in the UITabBarController
  • Control whether the tab bar overlays content or sits below it

UIKit gives you extensive flexibility without requiring much code.

Testing your UITab-Based app

As simple as this example may seem, real-world apps often contain complex navigation paths, asynchronous data loading, and device-specific UI behavior. Testing tab navigation manually can become time-consuming and error-prone as apps scale.

This is where Tricentis Testim Mobile adds significant value. By offering AI-powered mobile test automation with both coded and no-code options, Tricentis Testim Mobile enables teams to validate UI flows—like tab navigation, view switching, and user interactions—across devices without slowing down development.

Moving on

The tab bar remains one of the most reliable and widely used navigation patterns in mobile development—across both iOS and Android. Its simplicity, usability, and familiarity make it a foundational tool every mobile developer should master.

If you want to take your Swift development skills further, understanding UITabBarController is essential. And if you want confidence that your UI behaves correctly in production, pairing solid development practices with Tricentis Testim Mobile ensures your app is ready for real users—without adding testing complexity

Author:

Guest Contributors

Date: Jan. 21, 2026
Author:

Guest Contributors

Date: Jan. 21, 2026

You may also be interested in...