Introduction
In today’s post, we are going to be discussing view controllers. Let’s start by talking about what a view controller is and it’s role in your iOS projects.
What is a View Controller?
View Controllers are at the core of every application. They act as sort of the glue between your models and your views. View controllers are both responsible for fetching/initializing your models as well as loading up your views to display the information within them.
Basic View Controllers
Every view controller that Apple provides extends from the base UIViewController subclass. This gives them a few basic properties that you will find handy. The most important of course is the view property. This is a reference to the view that the controller is managing.
Along with some default properties, you also get some powerful methods that allow each UIViewController subclass to easily interact with all of the common view controllers.
Here are some of the basic view controllers to be discussed in this post:
- UINavigationController
- UITableViewController
- UITabBarController
I picked these 3 controllers since they are the most common and are automatically created in the main project templates when creating a new XCode project.
UINavigationController
Generally we don’t talk about UINavigationControllers outside of the context of a UITableViewController, however we need to discuss them prior in order to see what’s going on under the hood.
UINavigationController are used when you want to have some sort of hierarchal representation of your data (ie drill down). They work using a stack of UIViewController subclasses. Every time you “drill down”, you simply add another view controller to the stack. Then, the “back” logic is simply a matter of popping view controllers off of a stack.
At the bottom of the stack, there is the rootViewController which get set during initialization and can never be popped off. At the top of the stack, is the current view controller who’s view is being displayed to the user.
Here is a code sample demonstrating how to push a view controller on to the navigation stack.
// Initialize MyViewController object MyViewController *controller = [[MyViewController alloc] initWithNibName:@"MyViewController" bundle:[NSBundle mainBundle]]; // Pushes MyViewController object on the navigation stack [self.navigationController pushViewController:controller animated:YES]; [controller release];
In this example, we instantiate a new UIViewController subclass called MyViewController and push it on to the view controller stack. One very important detail to point out is, you must do this from inside of a view controller that is contained inside of a navigation controller, otherwise the “self.navigationController” property will be nil. navigationController is a property of every UIViewController subclass that gets set whenever it has been inserted into a UINavigationController.
Another thing to note is, we release the controller after pushing it on to the stack (prior to iOS5 with ARC). This is because the navigation controller will retain your view controller therefore preventing it from being released from memory.
The following code will pop the view controller off of the view stack with an animation.
[self.navigationController popViewControllerAnimated:YES];
Now we are going to see how UINavigationControllers are used in conjunction with UITableViewControllers.
UITableViewController
UITableViewControllers are quite possibly the most popular/common view controllers used in iOS. They are perfect for displaying large amounts of data or any sort of form entry.
By default, a UITableViewController has a UITableView as it’s main view and serves as both the delegate and data source for this view. What this means is, it is responsible for populating the table with data, configuring how the data is displayed, and determining what to do when the user interacts with the table (swipes, touches, etc…).
Let’s take a look at a simple UITableViewController. Here is an init method initializing the data array to be displayed in the table view as well as the primary 3 data source methods.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { self.title = NSLocalizedString(@"Sites", @"Sites"); // #1 _dataArray = [[NSArray alloc] initWithObjects:@"Reddit", @"XKCD", @"The Oatmeal", @"Hacker News", @"iCodeBlog", nil]; } return self; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // #2 return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // #3 return [_dataArray count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } // #4 cell.textLabel.text = [_dataArray objectAtIndex:indexPath.row]; return cell; }
As mentioned before, we are displaying a simple array of NSString objects in the table view. This array gets initialized in #1 before the tableview is even displayed. The next method in #2 returns the number of sections for the table. In our case, we only need one section so we hard code the number 1 as the return value.
The code in #3 returns the number of cells in a given section. Generally, this will mean the number of objects in your array. So, we just return the count property of our dataArray.
Finally, the method in #4 builds the cell for a given row in a section, configures that cell, and returns it. In our case, the only configuration we need to do is set the text property of the cell’s UITextLabel to the string in our array at the corresponding index.
These 3 methods are the absolute minimum required to use a UITableViewController and display its table. There are quite a few more methods that you can implement related to the cell height, titles for sections, and more. The last thing I want to show you is how to support basic user interaction.
There is only one method you need to implement to support taps on the cells. Here is the sample code from above inside of didSelectRowAtIndexPath.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // Initialize MyViewController object MyViewController *controller = [[MyViewController alloc] initWithNibName:@"MyViewController" bundle:[NSBundle mainBundle]]; // Forward some data controller.site = [_dataArray objectAtIndex:indexPath.row]; // Pushes MyViewController object on the navigation stack [self.navigationController pushViewController:controller animated:YES]; [controller release]; }
Now when the user taps a row, it will loading up a MyViewController object, pass along the information about the cell tapped and push it on to the view stack. One thing to note here is, this code assumes that the UITableViewController class presented above is inside of a UINavigationController.
UITabBarController
The last controller we are going to discuss in this post is the UITabBarController. As you may have seen in many applications, a tab bar separates out view logic into multiple “tabs” and switches between them when the user taps on each one. Each tab represents a different view controller that could be any of the types above. As a newer developer, TabBarControllers are a little tricky to implement manually, so I’d suggest starting your project using “Apple’s Tab Bar Application” template file. This will provide you with a simple tab bar application with two sample tabs.
We are going to walk through the code generated from the iOS 5 template that details how a tab bar is constructed.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; // #1 UIViewController *viewController1 = [[[FirstViewController alloc] initWithNibName:@"FirstViewController" bundle:nil] autorelease]; UIViewController *viewController2 = [[[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil] autorelease]; // #2 self.tabBarController = [[[UITabBarController alloc] init] autorelease]; // #3 self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1, viewController2, nil]; // #4 self.window.rootViewController = self.tabBarController; [self.window makeKeyAndVisible]; return YES; }
The code here is actually pretty straight forward. #1 initializes both of the view controllers we want to display in 2 separate tabs. Don’t worry about setting their title and tab bar icon now, I will get to that in a moment.
Now, in #2 we initialize the tab bar controller. Unlike the UITableViewController, we don’t need to make our own subclass. It is fine to just let the app delegate manage an instance of the tab bar controller.
The code in #3 is where we set the view controllers of the tab bar controller. It should be clear now how easy it is to add more “tabs” to you your application.
Finally, #4 adds the tab bar controller to the window displaying it to the user.
Let’s take a quick look at the code in the initWithNibName method of FirstViewController in order to see how the title and image are set for that controller.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { self.title = NSLocalizedString(@"First", @"First"); self.tabBarItem.image = [UIImage imageNamed:@"first"]; } return self; }
Pretty simple right? Every UIViewController subclass has a title and tabBarItem property. That way, it knows what to display if you place it inside of a UITabBarController.
Conclusion
This wraps up our very surface level introduction to UIViewControllers. I have zipped both of the sample projects used here and you can download them here.
If you have any questions or comments, please feel free to leave them here or write them to me on Twitter.
Happy iCoding!
This post is part of an iOS development series called Back To Basics. You can keep up with this series through the table of contents, RSS feed, or Twitter.