Friday, April 24, 2009

iPhone tutorial: "Navigation-Based Application" part 2

In the last part we mainly analysed the IB files of the "Navigation-Based Application" XCode template. This time we're going to look at the source and also do some modifications to it. This part mainly focuses on the table view (UITableView) so the title of this post could have been "UITableView tutorial".

If you scroll through "Classes/RootViewController.m" in XCode, you'll notice that it mainly consists of methods that are commented out. The reason for why there are so many methods is that this class has a lot of "responsibility". If you double-click on Resources/RootViewController.xib in XCode to load the file into Interface Builder (IB) and then CTRL-click on "File's Owner" in the RootViewController.xib window in IB, you'll see that it has two referencing outlets; 'Table View.dataSource' and 'Table View.delegate', which means that our RootViewController object has been connected to these properties of the "Table View" object in this IB file. The 'tableView' property of our RootViewController has in turn been connected to the "Table View" object. This probably means that we'll receive messages from "Table View"...

Since "Table View" is of the class UITableView, we should look up that class in XCode's API docs window. Under the "Properties" section in the docs for UITableView we see that the object assigned to the 'dataSource' property should implement the UITableViewDataSource protocol and the object assigned to the 'delegate' property should implement the UITableViewDelegate protocol.

That's what we meat by a lot of responsibility above - it is expected to implement these two protocols. The 'dataSource' is intended to supply the actual contents of the table, while the 'delegate' focuses on the presentation of the contents.

UITableViewDataSource

According to the API docs in XCode this protocol "provides the the table-view object with the information it needs to construct and modify a table view.", but also that it "supplies minimal information about it's appearance".

As usual in protocols, a lot of the methods are optional. The only required methods in this protocol are 'cellForRowAtIndexPath' and 'numberOfRowsInSection'. UITableView's definitoin of a table is that it has a number of rows, where each row has a cell which contains the actual contents. Furthermore, a table can be divided into sections, where each section contain a number of rows. This way rows that belong together can be "grouped" and each group (or section) can have its own title, etc.

That should explain the "row", "cell" and "section" parts of the method names, but what on earth is an "index path"?

Index paths

One way to visualise an index path is to think of a document which is divided into chapters, sections, paragraphs, sentences and words. If you want to "point at" a specific word in the document you could specify that it is in chapter 3, section 2, paragraph 1, sentence 7 and word 5. The "path" to that word would be 3, 2, 1, 7, 5 or perhaps /3/2/1/7/5 to make it look more like the paths used to navigate the file system on a computer or page structure on a web site.

If you wanted to present the contents of a book hierarchically using tables you could start by presenting a table listing all the chapters in the book. When the user selects one of the chapters from the table, you switch to a table listing all the sections in that chapter. When the user selects a section, you switch to a table of all the paragraphs in that section, and so on...

In this setup you would have a chapter table, section table, paragraph table, sentence table and word table. All tables of a certain kind (chapter, section, etc.) would look alike (visually), but would have different content, since chapter 2 probably doesn't contain the same sections as chapter 1, etc.

Thus, index paths can be used to "point at" or reference a specific item in a hierarchy of items or a specific node in a tree if you're familiar with that term. In our context it is used as a reference to a specific row in a hierarchy of tables. If you want to know even more about "index paths" I suggest you do a seach for NSIndexPath in XCode's API docs window since that is the class which implements index paths.

tableView:numberOfRowsInSection:

As we said before, a table consists of a number of rows which can be divided into sections ("grouped together"). Even though that it sounds like a "section" is a new level in the hierarchy of tables, that is not the case. Instead, it is a "level" inside the table. Say that we have a table with 5 rows, divided into two sections; the first section has 2 rows and the second has 3:

row 0: section 0, row 0
row 1: section 0, row 1
row 2: section 1, row 0
row 3: section 1, row 1
row 4: section 1, row 2

We see that there are two ways to "point to" a specific row in this table; either you specify only the row (0-4) or you speficy the section and row within the section. We also see that it is possible to translate between these two types of "pointers"; row 3 is the same as "section 1, row 1", for example.

A more compact way of describing the "structure" of the table above would be

section 0: 2 rows
section 1: 3 rows

and that is exactly the method which UITableView uses and the information that the  'numberOfRowsInSection' method should supply. The method gets called with the section (0, 1, ...) as an argument and is expected to return the number of rows for that section.

By the way, did you notice anything strange with the title of this section? See that the method name actually starts with "tableView:"? In Objective C, you could say that the "name" of this method actually is "tableView" and that it has an argument called "numberOfRowsInSection".  Or you could say it's a method with no name that has two arguments; 'tableView' and 'numberOfRowsInSection'. Ok, let's stop playing around - the name of this method is "tableView:numberOfRowsInSection" and it has two arguments - nothing strange about that! ;)

So, what is the 'tableView' argument good for? That is used to identify which tableView that wants to find out how many rows it has. Specifying the tableView makes it possible for a single object to be the data source for multiple (different) tables.

numberOfSectionsInTableView:

If you want to create a table with more than one section you have to implement the optional method 'numberOfSectionsInTableView' as well. Otherwise it is assumed that the table only has one section (section 0).

Note that this method, just like 'numberOfRowsInSection', specifies the tableView requesting information, but here it is the last argument instead of the first. Don't ask me why.

tableView:cellForRowAtIndexPath:

Now that the structure of the table is defined it's time to fill it with some contents. That's when we enter the world of the cells, since it is those that contain the actual contents. A specific cell in a hierarchy of tables is referenced using an "index path", just as we referenced specific words in a book in the explanation of index paths above. An UITableView will ask you to supply a cell for it by giving you an index path - a brutal but quite simple and effiecient way of solving a rather complex problem.

This method should return a pointer to an UITableViewCell object, so let's start by looking at the API docs for that class in XCode. Whoa, that's a huge document! I think we'll limit ourselves to the basics in this part of the tutorial.

initWithFrame:reuseIdentifier:

It can't get more basic than creating and initialising an object, and initialise is exactly what this method does. If you wonder how to create  the object you, as always, call the alloc-method on the class; [UITableViewCell alloc].

If you check the XCode API docs for this method, you'll quickly find out that it is possible to call this method with CGRectZero (a "zero sized" rectangle) as the 'Frame' argument and nil as the 'reuseIdentifier' argument. So what are those arguments for if it you more or less can skip them? The API docs go on to say that the 'Frame' needs to be properly specified if you need a complex layout for the cell's contents and 'reuseIdentifier' is used if you want to reuse a cell. Being able to reuse a cell is a performance optimisation and as often is the case, added performance leads to added complexity. This time in the form of a "strange" argument at initialisation. We'll return to this later.

Classes/RootViewController.m

Finally, we're ready to look at the source!

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

Optional method from the UITableViewDataSource protocol. Since it's optional and the default behaviour is to create a table with one section, we could have refrained from implementing this method.

// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 0;
}

Mandatory method from the UITableViewDataSource protocol. This method was thoroughly explained above. Returning 0 sounds a bit boring though, so I suggest you change this to 3 and build and run (CMD-Return). Wow, what a difference! No? You don't see any difference at all? Hmm, strange... But hey, let's try clicking on any of the first three rows in the table in the iPhone simulator. See, they light up in blue. Now try pressing any of the other rows. Nothing happens right? So the value 3 that we returned seems to have accomplished something, right?

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

Mandatory method from the UITableViewDataSource protocol.
    
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

I thought I wouldn't have to explain how to reuse table cells, but such a strange method name as 'dequeueReusableCellWithIdentifier' makes it hard to avoid, so here we go...

dequeueResuableCellWithIdentifier:

This method is part of the UITableView and the reason for that is that it is that class that actually "manage" the cells. It justs asks the 'data source' to create the cell for it, but once it is created it is managed by the table view. Remember that we mentioned 'reuseIdentifier' and something about performance optimisations when we discussed how to create and initialise a table cell? Well, this is another part of the optimisation scheme.

The scheme basically assumes that most (or many) cells in a table will look alike but contain different contents. For example, all cells (rows) might have a white background, but the text for each cell (row) might be different. So when the table view asks the data source for a cell, the data source can assign an "identifier" (name or  "description") for the cell. In our example this identifier could have been the string "cell with white background" or maybe just "white" or why not "cell 1" or just "1". Ok, I guess you get it, it can be named anything really.

When the table view gets this cell from the data source it caches it together with its identifier. Later when the data source needs to supply another "white" cell to the table view it can ask the table view if it still has it in its cache and that is exactly what this method does.

    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
    }

If the table view didn't know anything about the cell the data source wants to supply to it, the dequeue-method returns nil and that's why we check for nil here. This also means that we have to create the cell and that's why we call 'initWithFrame' and supply a 'reuseIdentifier'.
    
    // Set up the cell...

    return cell;
}

The comment seems to suggest that we should do something with the cell after creating and initialise it. Perhaps because it's a bit boring and not very useful having a table with just "blank" cells in it? Quickly checking the API docs for UITableViewCell in XCode we see that there is a property called 'text', so let's set it by inserting the following right above the return-line:

cell.text = @"My cell";

Build and run (CMD-Return) and - finally! - you should see somthing else than a boring white background with some thin grey lines on it! More specifically, you should see three rows with the text "My Cell".

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

Optional method from the UITableViewDelegate protocol, which, according to the API docs "Tells the delegate that the specified row is now selected". As you can see it references the row by supplying an index path.

    // Navigation logic may go here. Create and push another view controller.
// AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil];
// [self.navigationController pushViewController:anotherViewController];
// [anotherViewController release];
}

Lots of comments that suggest that we should do something here, which is a good idea because otherwise we would just have a "dead" table with almost no interaction. Sure, you would still be able see the contents of the table and actually scroll through it, so a "dead" table isn't completely dead, just a little boring.

To make it a little less boring, we'll log something to the console, so insert the following just above the final curly bracket:

NSLog(@"row %d was selected", indexPath.row);

Build and run (CMD-Return) and wait for the table to appear in the iPhone simulator. Once it has, select the source window in XCode and press (CMD-R) to bring the console window to the front. Go back to the simulator and press any of the "My Cell" rows and you should see something like this in the console window:

2009-04-26 08:17:19.693 Nav1[9992:20b] row 0 was selected

2009-04-26 08:17:20.868 Nav1[9992:20b] row 1 was selected

2009-04-26 08:17:21.676 Nav1[9992:20b] row 2 was selected


Summary

In part one I told you that the table view was a real beast and perhaps you agree by now. Sure, it isn't completely wild and unhandleable, but it sure needs to be tamed before it is of any use to you. We've come some way towards achieving that and now we actually understands what happens in this XCode template project, but I wouldn't be surprised if more will be written on the topic of table views in this blog...

Thursday, April 23, 2009

iPhone tutorial: "Navigation-Based Application"

The "Navigation-Based Application" template in XCode introduces us to the "navigation controller" (UINavigationController) and the "table view" (UITableView), which is a real beast, since you can use it for almost everything! Because of that, it is also very popular as you can see by playing around in the standard iPhone applications. If you are unsure where to look, check out "Settings" (the gears-icon on the home screen), since that is the most obvious user of the table view.

Table view is basically used to display a list of items in a table, where each row consists of a cell, which in its turn consists of a couple of "views" filled with content (text, icons, etc.). The table view also offers a "edit mode" which you can use to, for example, edit the contents of the cells or rearrange the rows.

This template also introduces a lot of other concepts, so this tutorial will probably be broken into many parts.

Enough talk! Start up XCode and choose "File/New Project" from the menu and select the "Navigation-Based Application" template and name the project "Nav1". Expand the Classes and Resources groups in the XCode's "Groups and files" section in the left of XCode's main window that. This shows you that the project has the following structure:

Nav1
Classes
RootViewController.[hm]
Nav1AppDelegate.[hm]
Resources
RootViewController.xib
MainWindow.xib
Info.plist

Click on Info.plist to confirm that "MainWindow.xib" is set as the "Main nib base name". After that click on Nav1AppDelegate.h. If you've read the previous posts, you'll see that it starts out in a standard manner; it implements the UIApplicationDelegate protocol and declares a 'window' property (UIWindow). After that a UINavigationController called 'nagivationController' is declared, which we haven't seen before. Both these are defined as properties and marked as IBOutlet so that we can manipulate them from Interface Builder.

Classes/Nav1AppDelegate.m

- (void) applicationDidFinishLaunching:(UIApplication *)application {
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}

When we implement the 'applicationDidFinishLaunching' method from the UIApplicationDelegate interface we are given a chance to "do stuff" right after the application has been loaded, so that everything will look good when it finally appears on the screen. Here we decide that the view of our UINavigationController should be displayed at startup and therefore we add it as a subview to our window by calling addSubView. We also make our window visible and ensure that it is the "first responder" (of events) by calling makeKeyAndVisible.

Ok, now that we have some understanding of what application does at startup, let's take it for a test run by building and running (CMD-Return). Not too exciting, huh? A white screen with a blue bar at the top. But wait, if you look carefully, you'll also notice that there are some thin grey horizontal lines evenly spaced down the white background. Yep, that's a table view with empty cells! The thin lines you see are the row separators.

Resources/MainWindow.xib

Double-click on the file in XCode to start Interface Builder (IB). As always when working with non-trivial IB-files, it's best to switch view mode to the hierarchical mode (middle button above the text "View Mode" in the upper left corner of the MainWindow.xib window in IB). After doing this, you'll see that the IB-file has the following structure:

File's Owner [UIApplication]
First Responder [UIResponder]
Nav1 App Delegate [Nav1AppDelegate]
Window [UIWindow]
Navigation Controller [UINavigationController]
Navigation Bar [UINavigationBar]
Root View Controller [RootViewController]
Navigation Item [UINavigationItem]

File's Owner [UIApplication]

CMD-4 shows nothing special. CMD-2 shows us that the 'delegate' property is connected to an object called "Nav1 App Delegate", which is ours.

First Responder [UIResponder]

Nothing special.

Nav1 App Delegate [Nav1AppDelegate]

CMD-4 shows the class as Nav1AppDelegate which we already knew since we're using the hierarchical view mode in the MainWindow.xib window. Our properties 'window' and 'navigationController' are also shown here. CMD-2 shows us that 'window' is connected to an object called "Window" and 'navigationController' is connected to an object called "Navigation Controller". Both these are explained below. We also see that the "File's owner".delegate property is set to point to this object.

Window [UIWindow]

CMD-4 shows it's a plain old UIWindow. CMD-2 shows that the "Nav1 App Delegate".window property is set to point to this object.

Navigation Controller [UINavigationController]

CMD-4 shows it's a UINavigationController that has one outlet called 'delegate'. Without knowing the details of how a navigation controller works, we can guess that 'delegate' should point to an object which wants to receive messages from (having it's methods invoked from) this object. CMD-2 shows that this object has four outlets in total, but on CMD-4 we only saw one ('delegate'). This suggests that UINavigationController is a subclass and if you're memory is good you probably recognise the 'navigationItem', 'tabBarItem' and 'view' outlets from an UIViewController.

Let's verify this by going back to XCode, select Nav1AppDelegate.h, double-click on UINavigationController to select that word in the source code. Position the mouse over the selected word and CTRL-click and select "Find Selected Text in API Reference". This should bring up a window containing the documenation for the UINavigationController class. There you can see that it inherits from UIViewController : UIResponder : NSObject. Good.

CMD-2 also shows that the "Nav1 App Delegate".navigationController property is connected to this object.

Navigation Bar [UINavigationBar]

CMD-4 shows that this is a UINavigationBar with a 'delegate' outlet. CMD-2 shows that none of its outlets are connected.

Root View Controller [RootViewController]

CMD-4 shows the class is RootViewController, which is one of our classes, and has no outlets (of its own). CMD-2 however shows the standard UIViewController outlets so this class apparently inherits from that class. The 'navigationItem' outlet is connected to an object called "Navigation Item". If we search for 'navigationItem' in the XCode API docs window which we popped up a while ago and click on the search result which represents the property of that name, we read that the navigation item referred by the property is used to [visually] represent the view controller when it is "pushed onto a navigation bar". Is it some kind of icon or what? Read on and you'll find out.

CMD-1 normally doesn't show anything exciting, but here it does. It shows that this object will be loaded from an IB-file called "RootViewController", which exists in the Resources group of our XCode project. If you've read the previous posts, you are familiar with the concept of a "placeholder" (proxy) object like this one.

Navigation Item [UINavigationItem]

CMD-4 shows the class as UINavigationItem and a rather long list of outlets; 'backBarButtonItem', 'leftBarButtonItem', 'rightBarButtonItem' and 'titleView'. CMD-2 shows that none of these properties are connected to anything. Maybe that's why we saw no buttons and no title when we test ran the application from XCode in the iPhone simulator? Sounds reasonable, but to really understand what these outlets are for I suggest you search for 'UINavigationItem' in the API docs window in XCode.

From the API doc we can read that a navigation bar (the blue bar at the top on the screen you saw when you test ran the application) normally displays a "Back"-button to the left and a title in the center. The "Back"-button is normally labelled with title of the "back" (previous) screen, but you can override this by setting the 'backBarButtonItem' property. The docs also indicates that if you accept the standard behaviour, you won't need to set any of the properties. Good, that means that we can leave them for now...

Or wait, not just yet! If you press CMD-1, you will see that there are some edit boxes for "Title" "Prompt" and "Back Button" which lets you set the text for those properties. This means, that if you are satisfied with just having a custom text - compared with having custom buttons - you can enter the text here. Try entering "MyTitle", "MyPrompt" and "MyBack" in the boxes and then take a look at the MainWindow.xib window. See that a small arrow appeared to the left of "Navigation Item"? Press it to expand the hierarchy and you'll see that a "Bar Button Item (MyBack)" object was added. If you're really observant you'll also see that "(MyTitle)" was added on the row of the "Navigation Item". Here IB offers a really simple way of customising the navigation controller - it even creates the necessary "Bar Button Item" object for you.

The "MyPrompt" text we entered isn't visible in the MainWindow.xib window, but if you double-click on the "Navigation Item (MyTitle)" row in the window, the "edit window" will pop up and you'll be able to see the navigation controller we are designing. There, at the top of the screen, you'll see the "MyPrompt" text as well. That text is actually assigned to the 'prompt' property. After seeing this you should delete the text though, because setting it seems to trigger a bug in XCode which makes it impossible to build the application.

Bar Button Item (MyBack) [UIBarButtonItem]

CMD-4 shows that the class is UIBarButtonItem. CMD-2 shows that there are no outlets, but it also shows a section called "Sent Actions" which we aren't used to seeing here. We might return to that later.

Ok, save the changes in IB (CMD-S) and go back to XCode and build and run (CMD-R). You did remember to clear the "Prompt" field of the "Navigation Item" right? Otherwise it won't build. Now the application should look a bit more exciting since it displays "MyTitle" in the blue navigation bar at the top of the screen.

Resources/RootViewController.xib

This is quite small IB file which has the following structure.

File's Owner
First Responder
Table View

Remember that this file is intended to be "loaded into" one of the objects in MainWindow.xib? This was configured by setting the "NIB name" attribute (CMD-1) of "Root View Controller" to "RootViewController". This means that "Root View Controller" will "own" this file, which brings us to...

File's Owner [RootViewController]

CMD-4 shows that the class of this object is our class RootViewController - that is an object of that class will "own" this file. It also shows that it has no outlets of its own, but if we take a brief look at RootViewController.h in XCode we can see that it is a subclass of UITableViewController, which in turn is a subclass of UIViewController. This means that at least the standard view controller outlets are available.

CMD-2 indeed shows that there are quite a lot of outlets; 'navigationItem', 'tabBarItem', 'tableView' and 'view'. We have seen three of these before, but 'tableView' is new and comes from UITableViewController. An interesting thing here is that both 'tableView' and 'view' are connected to the same object - "Table View". This makes sense since "Table View" is of the class UITableView which inherits from UIView, so 'view' is connected to the "UIView part" of "Table View".

CMD-2 also shows that there are two referencing outlets, that is, properties of other objects that are connected to this object; "Table View".dataSource and "Table View".delegate to be precise.

First Responder [UIResponder]

As always(!) quite uninteresting...

Table View [UITableView]

CMD-4 shows it is of the class UITableView and has a 'dataSource' outlet. A search for UITableView in the XCode API docs window brings up the docs for that class and after expanding the Properties section in the left frame we can click on 'dataSource' and read that it should be set to an object which implements the UITableViewDataSource protocol. Now that we know that we can move on.

CMD-2 shows two outlets; 'dataSource' and 'delegate', but where did 'delegate' come from since we didn't see it under the Identity tab (CMD-4)? Yet another look in the XCode API docs window for UITableView shows that it is a subclass of UIScrollView and that's where the 'delegate' property comes from. It also shows that the protocol implemented by 'delegate' should be UIScrollViewDelegate.

Both these properties are connected to "File's owner" which is the RootViewController in MainWindow.xib. This means that that object will receive messages from both the UITableView and the UIScrollView part.

Finally, we see that there are two referencing outlets. Once again that means that some other object's outlets have been connected to this object. Here it is "File's Owner".tableView and "File's Owner".view that has been connected to this object.

Time for a break

Phew! I initially told you that this template would introduce a lot of new concepts and it sure did! All this text and so far we have only analysed the IB files and a little of the source code and still we only have an empty table! At least I need a break now, so the rest of the analysis and some more interesting stuff will follow in part two.


Monday, April 20, 2009

iPhone tutorial: "Tab Bar Application" part 2

In the last post I mentioned that I would probably write a short post commenting on the source of the XCode "Tab Bar Application" template. There really isn't that much to say about the source since there is very little of it - almost everything we see when running the app happens because of the objects in the xib files.

Let's take a look at one thing though. Go to XCode and select Classes/Tab1AppDelegate.m. Scroll down to the method 'didSelectViewController' and un-comment it. Edit the source so that the body of the method looks like this:


NSLog([NSString stringWithFormat:@"didSelectViewController:%@", viewController.title]);

Run the program by pressing CMD-Return and after the iPhone simulator window appears, select the XCode window and press CMD-R to bring the debugger window to the front. We do this, since this is where the text produced by NSLog() appears.

didSelectViewController is part of the UITabBarControllerDelegate protocol so we can expect the tab bar to "send this message" to us, that is, call this method when we press one of the buttons on the tab bar. Let's try that. Yes, go ahead and press the tab bar buttons in the simulator and see what happens. Did you remember to press CMD-R in XCode to bring the debugger window to the front by the way? If not, do it now.

Ok, so what happened? Not much, huh? Have we found a bug in the iPhone API or why isn't the method called? No, it's not a bug in the iPhone API but maybe in the XCode template application. Or more specifically in MainWindow.xib. To resolve the problem/bug, double-click on Resources/MainWindow.xib to start Interface Builder (IB). In IB go to the MainWindow.xib file and select "Tab Bar Controller" and press CMD-2. See that it has a 'delegate' outlet which isn't connected to anything? That's probably the explanation to the problem. Let's connect it to "Tab1 App Delegate" and see if that works better.

Remember that you have to "think backwards" (atleast in my view) to connect an outlet by CTRL-dragging in IB? Ok, start by positioning the mouse over "Tab Bar Controller". After that press and hold CTRL while you drag the mouse until it's positioned over "Tab1 App Delegate". Now release the mouse and a window should pop up. In that window, click on 'delegate'. This should have connected the 'delegate' outlet of "Tab Bar Controller". To verify this, select "Tab Bar Controller" and press CMD-2. A connection now exists to "Tab1 App Delegate", right? If not, try again! When you have succeeded, save the IB file (CMD-S).

Return to XCode and build and run (CMD-Return). Wait for iPhone Sim to start up and then select the source window in XCode again and bring up the debugger window (CMD-R).  Play around in the simulator a bit by pressing the two buttons and you should see something like this in the debugger window:

2009-04-20 22:40:14.842 Tab2[5188:20b] didSelectViewController:First 2

2009-04-20 22:40:19.972 Tab2[5188:20b] didSelectViewController:(null)

2009-04-20 22:40:22.026 Tab2[5188:20b] didSelectViewController:First 2

2009-04-20 22:40:23.227 Tab2[5188:20b] didSelectViewController:(null)

2009-04-20 22:40:24.506 Tab2[5188:20b] didSelectViewController:First 2

2009-04-20 22:40:25.746 Tab2[5188:20b] didSelectViewController:(null)

2009-04-20 22:40:27.139 Tab2[5188:20b] didSelectViewController:First 2


Wow, it works! But why do we get a "(null)" title for one of the view controllers? The second one to be specific. Go back to IB and select "First View Controller" in MainWindow.xib and press CMD-1. See that the 'title' is set to "First 2"? That's because it was set to that name in the "View Controllers" table in the "Tab Bar Controller" object in XCode (select it and press CMD-1 to verify). However, if you select the second view controller in MainWindow.xib - the one called "View Controller (Second)" and press CMD-1, you'll see that the title here is empty even though it had a name in the view controllers table in "Tab Bar Controller".

This probably has to do with the fact that the second view controller is defined in and loaded from another xib-file - Resources/SecondView.xib - but I'm not completely sure. To remedy this and set the title, select "View Controller (Second)", press CMD-1 and edit the title. Save the xib file (CMD-1) and return to XCode and build and run (CMD-R) and you'll see that it works.


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...