Sunday, April 19, 2009

iPhone tutorial: Analysing XCode's "Tab Bar Application" template

If you've read the previous posts you probably know where to start, but here comes a brief procedure: Start XCode, choose "File/New project" from the menu, select the "Tab Bar Application" icon and name the project "Tab1". After XCode has created the project for you, expand the "Classes" and "Reosources" groups in XCode's "Groups and files" frame, you'll see that this template contains the following:

Tab1
  Classes
    FirstViewController.[hm]
    Tab1AppDelegate.[hm]
  Resources
    SecondView.xib
    MainWindow.xib
    Info.plist

To get a quick overview of what the application does, build and run it in XCode by pressing CMD-Return. The iPhone simulator should pop up and you should see a white background with the text "First View" on it. Below that is a block of text and at the bottom of the screen there are two grey-blackish buttons labeled "First" and "Second". If you press the button called "Second" you are presented with another white background, but this time with the text "Second View" on it. It also has a block of text and the two buttons labeled "First" and "Second" at the bottom.

The buttons at the bottom of the two screens are implemeted by using a "tab bar controller" (UITabBarController) which is used to easily switch between a number of  full screen views. In this context, full screen means the area above the buttons at the bottom, because the tab bar has reserved the bottom part of the screen for itself. If it didn't do this, it would be pretty hard to switch to another view since its button/tab would be obscured by the contents of the view.

After familiarising ourselves with the application we can do the following guesses about the files in project: Tab1AppDelegate.[hm] is the class implementing the UIApplicationDelegate protocol, FirstViewController.[hm] is probably used for implementing the view which had the text "First View" written all over it, MainWindow.xib is the IB file loaded automatically at start up (double check the "Main nib file base name" property in Info.plist to verify this) and SecondView.xib is the IB file describing how the second view should look like.

All this sounds logical, but some things seem a bit odd. Why isn't there a FirstView.xib file and no SecondViewController.[hm] files? We'll have to start digging to understand that and let's start with the h-files. 

Classes/Tab1AppDelegate.h

@interface Tab2AppDelegate : NSObject {


This class implements two protocols, the usual UIApplicationDelegate and a new acquaintance called UITabBarControllerDelegate. An educated guess whould be that the new protocol is used for "receiving messages" from a UITabBarController.


    UIWindow *window;

    UITabBarController *tabBarController;

}


@property (nonatomic, retain) IBOutlet UIWindow *window;

@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;


We have pointers to an UIWindow and an UITabBarController which we want to be externally accessible as well as being manipulated by Interface Builder (IB). Therefore we declare the pointers as properties as well as mark them with IBOutlet.

Classes/FirstViewController.h

Nothing exciting, it just declares that it is a subclass of UIViewController.

Resources/MainWindow.xib

Double-click it to bring up IB and immediately switch view mode in the MainWindow.xib window which appears in IB by selecting the middle button above the "View mode" text in the upper left corner of the window. This is the hierarchical view which you can explore by pressing the small arrows to the left of the objects to see the full hierarchy of objects in this xib-file. Press all the arrows you can find to fully expand the list into this:

File's owner [UIApplication]
First Responder [UIResponder]
Tab1 App Delegate [Tab1AppDelegate]
Window [UIWindow]
Tab Bar Controller [UITabBarController]
  Tab Bar [UITabBar]
  Selected First View Controller [FirstViewController]
    View [UIView]
      Label (First View) [UILabel]
      Text View [UITextView]
    Tab Bar Item (First) [UITabBarItem]
  View Controller (Second) [UIViewController]
    Tab Bar Item (Second) [UITabBarItem]

This view mode in IB is so much better than the default which only shows the top level objects as big icons! In this view you can see all the objects in the file and even their classes, which is great for quickly understanding what we're working with. If this view also had shown all the connections between the objects we would have known almost everything about the file...

Hmm, there actually is a way to see the connections. Not all at once which would have been really neat, but you can at least see them. Position the mouse pointer over the object (row) in the table you want to examine  and CTRL-click a window will pop up which shows all the connections (outlets and referencing outlets). You have to close this window manually so if you repeat the procedure for all objects you can actually see all the connections at once, even though there will be a lot of windows on your screen! ;)

Before starting to dig, let's play a little! You probably noticed that IB opened another window besides the MainWindow.xib window, namely a big iPhone-sized window displaying the text "First View" in a quite large font. Yep, that's the contents of this xib displayed graphically, that is, how the objects will be layed out (positioned) in the iPhone when we run the application. This is not a full simulation of the interface, but rather an "edit view" of the interface where we can manipulate the objects instead of just interact with them. Interaction is possible here too though as you can experience by pressing the "Second" button at the bottom of the window.

Something happened, right? But maybe not exactly what you expected? Instead of seeing a big bold text saying "Second View" as we did when we test ran the application in the simulator from XCode we see a rather modest text saying: View. Loaded from "SecondView.nib". When you pressed the "Second button" something less obvious also happened. What I'm talking about becomes visible if you activate the MainWindow.xib window. At the end of the list of objects in this window is the object "Selected View Controller (Second)".

Really observant readers might remember that in the "full hierarchy" list above there was no "Selected" text before this object's name. However, there was a "Selcted" before the "First View Controller (First)" object's name, which it no longer is. This "Selected" text thus indicates which view controller we currently have selected in the edit window. Press the "First" button and after that activate the MainWindow.xib window and you'll see that the "Selcted" text has been moved back to its original position. (The fact that you have to activate the MainWindow.xib window to see this change seems like a bug in IB.)

Speaking of the MainWindow.xib window. As you can see, some object names also contains some text within parentheses, like "First View Controller (First)" and "Label (First View)". The text within parentheses represent the "identifying" value of an object. A label is used to display a short text and thus the "text" property is shown. You can verify this by double clicking on the "First View" text in the edit window and change the text. After pressing return, the new text will be visible within parentheses in the MainWindow.xib window.

So where does the text "(First)" in "First View Controller (First)" come from and which property of a view controller does IB consider to be the "identifying" since it is displaying it here? That we'll find out when we start examining all the objects in detail, which we'll do right now!

Resources/MainWindow.xib

File's owner

CMD-4 shows us that the class is UIApplication (which we already knew since we're using the hierarchical view mode in the MainWindow.xib window) and CMD-2 shows us that the 'delegate' property is set to 'Tab1 App Delegate' (which we also can see by CTRL-clicking on the object in the MainWindow.xib window). This seems logicaly since we in the beginning of this post saw that Tab1AppDelegate.h declared that Tab1AppDelegate.m implements the UIApplicationDelegate protocol.

First Responder

Nothing exciting.

Tab1 App Delegate

CMD-4 gives us the class "Tab1AppDelegate" and the outlets 'tabBarController' of class UITabBarController and 'window' of class UIWindow just like we declared in Tab1AppDelegate.h. CMD-2 shows that 'tabBarController' is connected to an object named "Tab Bar Controller" and 'window' is connected to an object named "Window", both of which you can see in the hierarchical view of the MainWindow.xib window.

Window

CMD-4 says it's an UIWindow (surprise!) and CMD-2 says that the 'window' property of the "Tab1 App Delegate" object has been connected to this window.

Tab Bar Controller

CMD-4 says it's a UITabBarController and that it has an outlet called 'delegate', which probably means that this object is going to send messages to (call methods of) another object. Guessing some more, the receiving object should probably implement the UITabBarControllerDelegate protocol, just like we saw in Tab1AppDelegate.h. CMD-2 however shows that the 'delegate' outlet is unconnected - how disappointing! We'll have to examine that further in a while...

CMD-1 (attributes) shows the most exciting part of this object - a table called "View Controllers" with two columns "Title" and "Class". A tab bar controller controls a set of view controllers and this table specifies which objects that represent the view controllers.

If you want to read more about this right now I suggest that you check out the API docs for UITabBarController. This is easiest done by going to XCode, select the file Tab1AppDelegate.h, select the text UITabBarController in the interface definition and CTRL-click on the selected text. This should pop up a window where you can select "Find Selected Text in API Reference". This should bring up a new window with some search result in the upper half and text in the lower half. To the left there is a table of contents section where you should find the section "Properties". Expand that and you should see a property called 'viewControllers' - that's the property we are manipulating in the table in IB.

Ok, back to the "View Controllers" table in IB. Double-click on the Title-column in the first table row, right where it says "First". Edit the text to say "First 2" or something similar. Notice how the "(First)" text was changed to "(First 2)" in the MainWindow.xib window for the "First View Controller"? So this is where the "(First)" text came from - it's the view controller's title. If you return to the edit window, you'll see that this is the text presented on the button at the bottom of the screen, which selects this view controller.

Now try pressing the plus-sign ('+') just below the table. A new row should appear in the table and if you check the MainWindow.xib window you should notice that a new view controller and associated "Tab Bar Item" should have appeared at the end of the list. Then check the edit window to see that a new button also should have appeared. This is how easy it is to add a view controller to a tab bar controller. Remove the third controller we just created by selecting it in the table and pressing the minus-sign ('-') under the table or press backspace.

Tab Bar

This is more or less an "internal" object of the tab bar controller which we shouldn't mess with in IB, or anywhere really. This is what the API docs for UITabBarController says about it: 

"Although a tab bar controller uses a tab bar in its implementation, you should never need to, nor should you, access the tab bar directly."

First View Controller (First 2)
CMD-4 says it is of the class "FirstViewController", which is one of our classes - go check in XCode if you had forgotten about it. No outlets are shown here, but since it's a subclass of UIViewController (check the h-file in XCode) it of course has the standard outlets of a UIViewController. This can be seen in CMD-2 where we see that one of the standard outlets, 'tabBarController' is connected to an object called "Tab Bar Item (First)" and 'view' is connected to an object called "View". Both these are explained below, while the 'tabBarItem' is best understood by reading the API docs for UIViewController, especially the section about the 'tabBarItem' property. Basically, this property is used to defined how this view controller should be represented on a tab bar, that is what its "button" will look like.

CMD-1 gives the title as "First 2" - hey, that's what we changed it to in the "View Controllers" table in the "Tab Bar Controller" object a short while ago. The "NIB Name" is empty, but we'll soon see an example where it isn't - exciting, huh?

View

Neither CMD-4 or CMD-2 show anything surprising - it's a simple UIView.

Label (First View)

Neither CMD-4 or CMD-2 show anything surprising - it's a simple UILabel. CMD-1 on the other hand shows that the text-property is set to "First View" which is exactly the same as the text we saw when we test ran the application in XCode or look at the edit window here in IB. It's the text that the label should display - as simple as that!

Text View

Neither CMD-4 or CMD-2 show anything surprising - it's a simple UITextView. A bit more surprising is that CMD-1 doesn't show anything special either, so how did the text get into the view? Well, it's as simple as double-clicking the text view in the edit window and type what you want it to display. IB ensures that the text finally ends up in the UITextView's text-property.

Tab Bar Item (First 2)

CMD-4 and CMD-2 are both unexciting. CMD-1 says that the title is 'First 2' - here we go again. It seems as if the text we entered in the "View Controllers" table of the tab bar controller affected a couple of objects - first the view controller's title and now this. Read more about tab bar items by searching for UITabBarItem in the API docs in XCode.

View Controller (Second)

Ok, now the tricky part of this tutorial starts so keep focused. Remebered that the "second view" looked a bit strange when we switched to it in the edit window in IB? It said something about that this view was loaded from a nib. Double click on the "View Controller (Second)" row in the MainWindow.xib window and we'll see exactly what it said since this pops up the edit window. There we go: Loaded from "SecondView.nib".

If you take a look in the MainWindow.xib window you'll see another strange thing. The branch of the object hierarchy starting with "First View Controller" has lots of objects under it - View, Label, TextView, TabBarItem - while the branch starting at "Second View Controller" just has a TabBarItem. This is strange since the first and second views looked more or less identical - both had a title and some text. It turns out the View, Label and TextView items for this view are loaded from a another IB-file, "SecondView.xib" to be specific.

This is pretty cool. Instead of defining "everything" in a single IB file you can modularise your interface design into several IB files and then refer to them from a main IB file or similar. For a tab bar controller interface, this is very nice.

So how does this magic happen? Lets explore the object and see if we can find out. CMD-4 shows nothing special - it's an UIViewController with the standard outlets. CMD-2 shows that one of its outlets, 'tabBarItem', is connected to an object called "Tab Bar Item (Second)". It's also worth noting that the 'view' outlet isn't connected to anything. This is different from how it was in "First View Controller" (see above) and will therefore be explained shortly.

Tab Bar Item (Second)

This is the tab bar item for the second view controller. It is defined in the same way as the item for the first view controller. It's worth noting that it is defined here in MainWindow.xib and not in SecondView.xib though. The reason for that is that it "belongs to" the tab bar controller and tab bar which are defined in this IB file.

Resources/SecondView.xib

File's owner

You have remembered to switch the SecondView.xib window view mode to the hierarchical view mode (middle button above the text "View Mode"), right? Good, this should be made into a habit. All the "File's owner" object we have examined so far have been pretty boring, but this one actually is a bit interesting. Why? Press CMD-4 and you'll see that the class is "UIViewController". Interesting, can a view controller own (use) an IB-file? Well, apparently... Which view controller is it then? Well, it's "View Controller (Second)" in MainWindow.xib since we specified in the attributes window (CMD-1) that it should use an IB file called "SecondView". Sorry, no magic here either, just plain old logic.

Ok, did you really understand what was written above? That the "File's owner" object in this IB file is of the class UIViewController? You did? Good, let's continue then. As you know by now, UIViewControllers has three standard outlets (CMD-4 to see them). CMD-2 shows that the 'view' property of this view controller is set to an object called "View" which exists in this IB file.

What does it actually mean to set the 'view' property of the "File's owner" object? It means that we will set the 'view' property of the object which "loads" this IB-file and use it to initialise itself. In our case that is the "View Controller (Second)" object in MainWindow.xib. If you have a keen memory you will remember that we said it was a bit strange that that view controller did'nt have its 'view' property set (see above) and that we would explain that later. Well, later is now, and this is how it is set. Pretty neat, right?

The remaining objects in this IB-file are more or less identical to the corresponding objects under the "First View Controller" branch in MainWindow.xib so I won't bore you with the details here.

That's it for now. I'll probably write a short post explaining some things in the source code for this template too, but this post focused on the Interface Builder stuff...


2 comments:

  1. How do the UIViewController subclasses that each tab loads get resized to account for the tab bar space that's unusable because it's obscured by the tab bar?

    ReplyDelete
  2. I was wondering if you could answer a question I cannot seem to find an answer for if maybe you could help me.
    I am trying to do something similar to your project. I have first and second controllers, but also have another class called verify not associated with any view controller.
    In first controller I call verify in order to see if I should enable the second tab item, but having some problems. In my first class I disable the tab bar until I can call my verify class. I am able to go to verify class and come back to first, but no objects are useable again.
    Meaning I cant access the tab bar again when I come back from verify class. I can do an NsLog and print data from verify in a method of first controller class, but arrays, table view won't reload, nothing.
    I tried to access the tab bar in verify and its not accessible there either.
    which is probably not unusual, because the tab bar isn't associated with verify and I'm using this " [[[[self.tabBarController tabBar]items]objectAtIndex:1]setEnabled:FALSE];" to do so.
    I am affraid of using Appdelegate because there are multiple tab controllers for another part of my app
    Please help.

    ReplyDelete