Tab Bar Controller and class UITabBarController

In every app so far, the view controller has had a view underneath it, i.e., under its supervision and control. The view controller created the view in loadView() (although we often didn’t need to write this method of the view controller ourselves), triggered calls to the view’s drawRect(_:) method, and contained a property named view that referred to the view.

A special type of view controller (i.e., a subclass of class UIViewController) called a UITabBarController can have another view controller underneath it. In fact, a UITabBarController can have a whole array of view controllers underneath it, each with its own view. At any given moment, however, only one view controller in this array can display its view on the screen. The selectedIndex property of the UITabBarController tells us which view controller in the array is currently displaying its view.

A UITabBarController lets us switch back and forth in any order between the view controllers beneath it. Each of these other view controllers has a view of its own. Our example has five view controllers under the tab bar controller, one for each borough. For simplicity, all five belong to the same class, but they could belong to five totally different classes. See the iOS Clock app for an example.

Keep the red badges short, otherwise they will block the icons. If a tab has an icon, the badge will be in the tab’s upper right corner. If the badge has no icon (i.e., if tabBarItem.image == nil), the badge will be in the upper left corner.

Apple’s documentation

  1. Tab Bar and Tab Bar Icons, and Bar Button Icons in general, in the iOS Human Interface Guidelines. See UITabBarSystemItem for a list of Apple’s standard icons.

  2. Tab Bars in the UIKit User Interface Catalog.

  3. About View Controllers in general. Class UIViewController.

  4. Tab Bar Controllers in particular. Class UITabBarController. (A future example will also need protocol UITabBarControllerDelegate.)

Source code in TabBar.zip

  1. Class AppDelegate. The application(_:didFinishLaunchingWithOptions) method of the application delegate creates an array of ViewControllers and puts it under the tab bar controller.
  2. Class ViewController. Its init method receives parameters from the ApDelegate. Its loadView method passes a parameter to the init method of its view.
  3. Class View
  4. Images.xcassets, an Xcode asset catalog file. Apple prefers .png format.
    1. Bronx_unselected.imageset
      1. Contents.json: a JSON file listing the files belonging to the Bronx_unselected image set.
      2. Bronx_unselected@2x.png: one of the files belonging to the Bronx_selected image set. 60 × 60 pixels.
    2. Bronx_selected.imageset
      1. Contents.json: a JSON file listing the files belonging to the Bronx_selected image set.
      2. Bronx_selected@2x.png: one of the files belonging to the Bronx_selected image set. 60 × 60 pixels.
    3. Brooklyn_unselected.imageset
      1. Contents.json: a JSON file listing the files belonging to the Brooklyn_unselected image set.
      2. Brooklyn_unselected@2x.png: one of the files belonging to the Brooklyn_selected image set. 60 × 60 pixels.
    4. Manhattan_unselected.imageset
      1. Contents.json: a JSON file listing the files belonging to the Manhattan_unselected image set.
      2. Manhattan_unselected@2x.png: one of the files belonging to the Manhattan_selected image set. 60 × 60 pixels.
    5. Queens_unselected.imageset
      1. Contents.json: a JSON file listing the files belonging to the Queens_unselected image set.
      2. Queens_unselected@2x.png: one of the files belonging to the Queens_selected image set. 60 × 60 pixels.
    6. Staten_unselected.imageset
      1. Contents.json: a JSON file listing the files belonging to the Staten_unselected image set.
      2. Staten_unselected@2x.png: one of the files belonging to the Staten_selected image set. 60 × 60 pixels.

Create the project

We have to tell the app that the first view controller it creates should be of Apple’s class UITabBarController, not of our class ViewController in the file ViewController.swift. Select the Main.storyboard file in the Xcode Project Navigator. Open the left pane of the center panel of Xcode as far as

▼ View Controller Scene
   ▶ View Controller
   First Responder
   Exit
and select the View Controller. In the right panel of Xcode, click on the icon for the Identity inspector. It’s a rectangle with a smaller rectangle in its upper left corner.
Custom Class
Class: UITabBarController
Module: (just leave it blank)

Add the five .png files to the Images.xcassets file of the project. See America.

The tab bar icons

For iPhone 6, each icon is 60 × 60 pixels and has a name that ends with @2x.png. I should have created additional icons without the @2x for older, non-retina devices. I created the icons with this Unix shellscript, but you probably have a better way of creating icons. Or you can get them from many places, including Glyphish.

The tab bar controller ignores the colors of the pixels in the icons. It pays attention only to the alpha level of each pixel. Pixels whose alpha is 0 are invisible; those whose alpha is 1 are drawn. In Bronx_unselected@2x.png, for example, the pixels along the edges and diagonal have alpha 1 and all other pixels have alpha 0. To make it possible to examine the .png file, I colored the pixels along the edges and diagonal black, and all other pixels white. But the tab bar controller ignores these colors.

The @2x.pngs are not mentioned in the application delegate, but the correct files are opened anyway, beased on whether the device is retina or non-retina.

Create the .png files with Adobe Photoshop

The tab bar is 49 pairs of pixels high on iPhone 6. The image in each tab bar item must be a 60 × 60 pixel .png file drawn with the alpha channel only. I created one with the Adobe Photoshop CS4 Extended version 11.0.1 at NYU.

File → New… Name: bronx
Width: 30 pixels
Height: 30 pixels
Color Mode: RGB Color
Background Contents: Transparent
OK

You now have a square file with a white and gray checkered background. It should be displayed in two places: in a little bronx @ 100% (Layer 1, RGB/8) window, and in the Navigator tab.

Select the pencil tool. Resize the pencil if necessary. The lines you draw will appear in blue against a gray background when the item is selected, and in gray against a black background when the item is not selected.

File → Save As…
Format: PNG
Save
PNG Options Interlace None
OK

Then add the png file to the Images.xcassets file of your Xcode project.

Things to try

  1. The above screenshots show that the tab bar blocks our view of part of the big yellow view, although the tab bar is translucent. How much of the big yellow view is blocked?
    	//immediately before the return true; at the end of the
    	//application(_:didFinishLaunchingWithOptions:) method of the app delegate
    
    	let tabBar: UITabBar = tabBarController.tabBar;
    	print("tabBar.frame = \(tabBar.frame)");
    

    iPhone 6 in portrait orientation:

    tabBar.frame = (0.0,618.0,375.0,49.0)
    

  2. The tab bar has room for only five tabs. If Yonkers becomes the sixth borough of New York City, where will it go? What about a seventh and an eighth borough?

    On the other hand, people get confused when they see a tab bar controller with less than three tabs. If you have only two views, you might want to do this instead.


  3. The Bronx has two icons, Bronx_unselected@2x.png and Bronx_selected@2x.png. Observe that they are photographic negatives of each other: when you select the Bronx, it turns into two blue isoceles right triangles. Brooklyn has only one icon, Brooklyn_unselected@2x.png, identical to Bronx_unselected@2x.png. When you select Brooklyn, the icon merely changes color.

  4. How could we make a 60 × 60 png alpha file of a Unicode character? ℞ ∫ ℵ

  5. In exercise 8 of Hello, we saw that the drawRect(_:) method of the currently displayed view is called when the orientation of the device changes. And the above screenshots show that the same is true for the present app: the width and height numbers are swapped. But what happens if we comment out the assignment to the contentMode property in the init method of class View? See ContentMode.Redraw vs. ContentMode.ScaleToFill.

  6. Apple has standard titles and icons for the most commonly used tabs, e.g. bookmarks and contacts.

    Let’s change the title and icon of Staten Island to Contacts. Change the init method of class ViewController to the following.
    	init(title: String, unselected: String, selected: String?, badge: String?, text: String) {
    		self.text = text;	//self.text is the property, text is the parameter
    		super.init(nibName: nil, bundle: nil);
    
    		if title == "Staten Island" {
    			tabBarItem = UITabBarItem(tabBarSystemItem: UITabBarSystemItem.Contacts, tag: 0);
    		} else {
    			self.title = title;
    			tabBarItem.image = UIImage(named: unselected);
    			if selected != nil {
    				tabBarItem.selectedImage = UIImage(named: selected!);
    			}
    		}
    
    		if badge != nil {
    			tabBarItem.badgeValue = badge!;
    		}
    	}
    
  7. Create a new project in Xcode. When it says “Choose a template for your new project” select “Tabbed Application” instead of “Empty Application”. Run the project and press the tabs. Select the Main.storyboard file and look at it.