Separating Controllers with UITabBarController – RubyMotion

Separating Controllers with UITabBarController

UITabBarController functions an awful lot like UINavigationController. The children controllers’ views fit above the black tab bar, where each tab corresponds to one child. The Music app shows a tab with room for four controllers:

The fact that the More tab appears here indicates there are more than five children.

Unlike other containers, UITabBarControllers are only to be used as the rootViewController of a UIWindow. You cannot push an instance of UITabBarController in pushViewController:animated:. From a user-experience perspective, this means you should use a tab bar only if contains very distinct and globally applicable controllers.

Just like UINavigationController, tab bars are easy to add. It just takes a small change to AppDelegate.

 controller = ColorsController.alloc.initWithNibName(​nil​, ​bundle: ​​nil​)
 nav_controller =
  UINavigationController.alloc.initWithRootViewController(controller)
 tab_controller =
  UITabBarController.alloc.initWithNibName(​nil​, ​bundle: ​​nil​)
 tab_controller.viewControllers = [nav_controller]
 @window.rootViewController = tab_controller

We create a UITabBarController like a normal UIViewController and set its viewControllers to an array containing our navigation controller. The order of viewControllers corresponds to the left-to-right order of the tabs.

When you run our app, you can see the top and bottom bars typical to most iOS apps implemented with a pretty small amount of code. Since it’s not very helpful to have just one unstyled tab, let’s fix that.

Every UIViewController has a tabBarItem property, which accepts UITabBarItem, an object containing information about how to draw the view for the controller in the bottom tab bar. It is not a UIView but rather a plain object that the system uses to construct a view. We use the UITabBarItem to customize the icon, title, and other appearance options for the controller’s tab.

The first step is to override initWithNibName:bundle: in ColorsController, and then we can create our UITabBarItem.

 def​ initWithNibName(name, ​bundle: ​bundle)
 super
  self.tabBarItem =
  UITabBarItem.alloc.initWithTitle(
 "Colors"​,
 image: ​​nil​,
 tag: ​1)
  self
 end

initWithTitle:image:tag: is one initializer for UITabBarItem, which we can use to set a custom image and title. tag: can be used to uniquely identify the tab bar item, but we won’t be using it here. image should be a 30x30 black and transparent icon. Setting image to nil means we won’t display images here.

You can also use the initWithTabBarSystemItem:tag: initializer to automatically set the title and image, assuming your tab corresponds to one of the default styles (such as Favorites or Contacts).

Why did we create our tab item in initWithNibName:bundle:? We want to create the tabBarItem as soon as the controller exists, regardless of whether its view has been created yet. UITabBarController will load the views only when absolutely necessary, so if you wait to create the tab item in viewDidLoad, then some controllers’ items might not be set when the app is done launching.

One more thing! We should probably add another tab, right? We’ll pretend this is the Top Color section, where we can view the most popular color. This way we can reuse our ColorDetailController.

 top_controller = ColorDetailController.alloc.initWithColor(UIColor.purpleColor)
 top_controller.title = ​"Top Color"
 top_nav_controller =
  UINavigationController.alloc.initWithRootViewController(top_controller)
 tab_controller.viewControllers = [nav_controller, top_nav_controller]

Run the rake command once again, and voila! You should see a whole bunch of container controllers like this:

Before we add even more content to our app, let’s take a moment to examine a few of the more subtle details of controllers.

The Edges of UIViewControllers

If you take a look at our second tab, you should notice that the purple background extends underneath the tab bar. In fact, all of our colored screens extend underneath the default iOS navigation elements. How does that happen?

Prior to iOS7, the navigation bar, tab bar, and other interface elements were opaque, so any interior controllers were sized to always be visible on the screen. But with the release of iOS7, many interface elements switched to a translucent visual effect, and Apple encourages apps to take advantage of that by layering their controllers and views.

This present a few problems: what if we don’t want important items to be hidden below the bars? What if we don’t want anything underneath? Apple provides a few methods on UIViewController to help us out.

If you don’t want any part of your controller to show up underneath the navigation chrome, you can use the edgesForExtendedLayout property of your UIViewController. For example, to prevent our second tab from leaking its color under the tab bar, we just change this property to UIRectEdgeNone:

 top_controller.title = ​"Top Color"
»top_controller.edgesForExtendedLayout = UIRectEdgeNone
 top_nav_controller =
  UINavigationController.alloc.initWithRootViewController(top_controller)
 tab_controller.viewControllers = [nav_controller, top_nav_controller]

Try out the app and see how the purple is no longer slipping under the navigation bar or tab bar. We could even set the edgesForExtendedLayout property somewhere in the definition of our UIViewController subclass, like viewDidLoad, if we never wanted this class to extend its edges.

Our controllers don’t do a whole lot, but you can see how these two classes form the building blocks of many iOS apps. UINavigationController and UITabBarController provide easy ways to organize many different parts of your app, but what if we really need to focus the user’s attention on just one screen? Well, it turns out that we can also present controllers modally in front of all other controllers.