After watching the course by Sean Allen about doing your UI 100% programmatically I was inspired to write a small blog post about the two ways to implement a UITabBar.
I will start with the storyboards and then show the programmatic way. Sometimes the storyboards method feels easier especially at the beginning perhaps, but when it comes to change the UI later, the programmatic way is much more flexible. At least I recommend to try both!
Storyboards are great, but they sure take up a lot of space!
First create a new Project in Xcode. Look for Tabbed App
.
Your storyboard will look like this:
It is ready to be edited. Run the project on the simulator and try it out. Both views will look like this out of the box:
The Tabbar Items can be customised easily in the inspector on the right:
First create a new Project in Xcode. Select on Storyboards and not SwiftUI.
Delete the `main.storyboard' file.
When in the project remove the references to the main storyboard in two places:
Making sure to hit the enter key:
In the General setting tab (press Enter when empty) and in the plist
file.
In iOS 13 Apple decided to split the AppDelegate.swift
file in two into an AppDelegate.swift
and a SceneDelegate.swift
. This allows for multi-windows operation. Before you had UIWindow and a window will be diaplayed. Now you have scenes. This allows to have two screens of the same app to be diswplayed at the same time, ex iPad. There is still one AppDelegate but now multiple scenes.
Inside SceneDelegate.swift
we have a window property. It used to be in AppDelegate.swift
. By default SceneDelegate.swift
gives us this hint already about the window property:
In SceneDelegate.swift
we need to specify our windowScene
property in the function scene(willConnectTo:)
which is the first function to run :
// we give a name to our variable
guard let windowScene = (scene as? UIWindowScene) else { return }
// our window will be the whole screen
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
//assign windowScene to the windowScene property of our window
window?.windowScene = windowScene
// assign the root controller to the window
window?.rootViewController = ViewController()
// and make it visible at start
window?.makeKeyAndVisible()
We can test it in our ViewController.swift
file using view.backgroundColor = .systemGreen
in viewDidLoad()
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGreen
}
}
The tabbar will hold our two navigation controllers.
Delete the file ViewController.swift
and edit the SceneDelegate.swift
.
Add:
window?.rootViewController = UITabBarController()
Make two new cocoa class files subclassing UIViewController and call them SearchVC and FavoritesListVC for example. As a functionality test we add a background color to their views. Feel free to choose a color:
// incidentally this supports the darkmode automatically!
view.backgroundColor = .systemPink
We now create two navigation controllers for our tabbar, which will hold an array of NavigationControllers (in our case two) and each of them will have an array of ViewControllers (in this case just one).
Edit the SceneDelegate.swift
as follows:
// create the nav controllers
let searchNC = UINavigationController(rootViewController: SearchVC())
let favoritesNC = UINavigationController(rootViewController: FavoriteListyVC())
// create tabbar with array of nav controllers
let tabbar = UITabBarController()
tabbar.viewControllers = [searchNC, favoritesNC]
Run the project, you should be able to select two screens, with no buttons. Still you will be able to tab between the two.
Now we will customize the two Navigation Controllers and the tabbar.
func createSearchNavigationController() -> UINavigationController {
// we create the view controller and insert into the nav controller and return
let searchVC = SearchVC()
searchVC.title = "Search"
// it is a system tabbaritem , tag zero because it is the first one
searchVC.tabBarItem = UITabBarItem(tabBarSystemItem: .search, tag: 0)
return UINavigationController(rootViewController: searchVC)
}
func createFavoritesNavigationController() -> UINavigationController {
let favoritesListVC = FavoritesListVC()
favoritesListVC.title = "Favorites"
// it is a system tabbaritem , tag zero because it is the first one
favoritesListVC.tabBarItem = UITabBarItem(tabBarSystemItem: .favorites, tag: 1)
return UINavigationController(rootViewController: favoritesListVC)
}
Refactor into a tabbar function
func createtabbar() -> UITabBarController {
let tabbar = UITabBarController()
// here we assign a tint to all our tabbars, this will be visible on the items (icons)
UITabBar.appearance().tintColor = .systemPink
// replace our array variables with the functions we created
tabbar.viewControllers = [createSearchNavigationController(), createFavoritesNavigationController()]
return tabbar
}
Our scene
function will be much cleaner and now look like:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
window?.rootViewController = createtabbar()
window?.makeKeyAndVisible()
}
Otherwise you can create a separate file with a tabbar class inheriting from UITabBarController and containing the functions createSearchNavigationController()
, createFavoritesNavigationController()
and initialising the tab bar.
Now running in the simulator you should be able to tab between these two:
As per Swift5.1 and Xcode the title and image of the system tab bar items cannot be changed. You cannot use the Apple icon and use a different title. They are defined as enum in obj c as follow. This is the list of the system options:
typedef enum UITabBarSystemItem : NSInteger {
case more, favorites, featured, topRated, recents, contacts,
history, bookmarks, search, downloads ,mostRecent, mostViewed
} UITabBarSystemItem;
This has a reason. Some icons are immediately recognisable across the whole iOS ecosystem and will have the same meaning to everyone, therefore it is not possible to use them differently than what for they were intended to. Also in this way Apple creates a better user experience across all its apps.
To use a custom icon and title select an it from in the storyboards like this:
The SF Symbols introduced in WWDC2019 are a great option. Download SF Symbols from the Apple website. Then open the app and select a symbol you like. Copy the name with CMD-Shift-C. Assign an icon and text to a Tabbar Item using SF symbols like "person.fill"
for the icon ane "Profile" for title as below:
func createProfileNC() -> UINavigationController {
let profileVC = ProfileVC()
profileVC.title = "My Profile"
profileVC.tabBarItem = UITabBarItem(title: "Profile", image: UIImage(systemName: "person.fill"), tag: 2)
return UINavigationController(rootViewController: profileVC)
}