Showing posts with label tutorial. Show all posts
Showing posts with label tutorial. Show all posts

Friday, March 12, 2010

Revisited: Storing and retrieving information using plists

Quite a while back I wrote a post about storing and retrieving information using plists. This has turned out to be one of the most appreciated posts in my blog, both by the number of readers and the number of comments. My post also contained some errors, but friendly commenters have helped eachother out in order to resolve those errors.

When I wrote the original post I hadn't really resolved how to store information on a real device in the proper way. For example, if you have a file in your application bundle which you want to update, you should start by copying the file from the bundle to the Documents-directory.

To resolve the issues from the first post and show the "proper way" of handling files that are updated by the application I simply created a new Window-based Application and edited the "applicationDidFinishLaunching" method to look like this:

- (void)applicationDidFinishLaunching:(UIApplication *)application {

// Override point for customization after application launch
[window makeKeyAndVisible];


// get the path to the "Documents" directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

// get the path to our plist ("Documents/foo.plist")
NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:@"foo.plist"];

// read or create plist

NSMutableDictionary *dict;
// check if our plist already exists in the Documents directory...
NSFileManager *fileManager = [NSFileManager defaultManager];
if ( [fileManager fileExistsAtPath:plistPath] ) {
// ...if it does, read it
NSLog(@"dict existed, reading %@", plistPath);
dict = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
} else {
// ...if it doesn't, create it
NSLog(@"dict didn't exist, creating...");
dict = [NSMutableDictionary dictionaryWithCapacity:1];

// Fill the dictionary with default values, either by copying
// a default plist from our bundle to the Documents directory
// or simply creating a new dictionary and writing it to th
// Documents directory. Here we choose to create a new
// dictionary rather than providing a default plist in the bundle.

// create a NSNumber object containing the
// integer value 1 and add it as 'key1' to the dictionary.
NSNumber *number = [NSNumber numberWithInt:1];
[dict setObject:number forKey:@"key1"];

// write dictionary to Documents directory...
NSLog(@"writing to %@...", plistPath);
[dict writeToFile:plistPath atomically:YES];
}

// dump the contents of the dictionary to the console
NSLog(@"dumping...");
for (id key in dict) {
NSLog(@"key=%@, value=%@", key, [dict objectForKey:key]);
}

// check if key2 is present
NSString *value2 = (NSString *)[dict valueForKey:@"key2"];
if ( value2 == nil ) {
NSLog(@"key2 didn't exist, adding...");
[dict setObject:@"default-2" forKey:@"key2"];
}

// dump the contents of the dictionary to the console
NSLog(@"dumping...");
for (id key in dict) {
NSLog(@"key=%@, value=%@", key, [dict objectForKey:key]);
}
}

The code should be rather self-explanatory thanks to the inline comments, but I really recommend you to read my first post on this subject to really understand what is going on.

A thing worth commenting on is the really strange path names logged to the console. These pathnames can look like this:

2010-03-12 09:09:15.557 Plist2[10780:20b] writing to /Users/henrik/Library/Application Support/iPhone Simulator/User/Applications/E10D7186-1219-4C52-804A-FE217F760967/Documents/foo.plist...

This path is a path in the Mac OS filesystem used by the iPhone simulator when testing an application. The strange part of it is the long string of hexadecimal numbers starting with E10D7. This is the so called GUID which is unique for each installed application. A lot has been written about this in other sources on the Internet, so I won't go further into that:





One annoying thing about it and that is that it changes each time you recompile your application. That means, that each time you build and test your application the files created by the application will be somewhere else in the Mac OS filesystem. This makes it hard to inspect the files written by your application. Fortunately, modern versions of XCode copy the contents from the "old path" to the "new path" so even though the GUID changes, the files created by the application the last time are available the next time you build and test it.

If you couple this annoying thing with the fact that nothing is logged to the console if you exit your application by pressing the home-button in the simulator and then relaunch the application by pressing its icon in the simulator. This means that if you want to see what is logged to the console you are forced to relaunch the application from XCode, which will create a new GUID and thus a new path in the Mac OS filesystem.

However, since XCode nowadays copies the contents from the old to the new path it is possible to test how the application responds to "external changes" such as removing a file stored by the application, etc. This can be very convenient to perform effecient testing of an application.

For the application in this post you can test how it behaves if "foo.plist" is present or not by first running the application from XCode (CMD-Return), observing which path is logged to console, removing that file and then re-running the application from XCode (CMD-Return). This will give you something like this on the console:

[Session started at 2010-03-12 10:18:24 +0100.]
2010-03-12 10:18:27.456 Plist2[10888:20b] dict existed, reading /Users/henrik/Library/Application Support/iPhone Simulator/User/Applications/DB3B1A75-BF08-461E-919A-DBFFCC994036/Documents/foo.plist
2010-03-12 10:18:27.464 Plist2[10888:20b] dumping...
2010-03-12 10:18:27.468 Plist2[10888:20b] key=key1, value=1
2010-03-12 10:18:27.469 Plist2[10888:20b] key2 didn't exist, adding...
2010-03-12 10:18:27.471 Plist2[10888:20b] dumping...
2010-03-12 10:18:27.475 Plist2[10888:20b] key=key1, value=1
2010-03-12 10:18:27.477 Plist2[10888:20b] key=key2, value=default-2

[Session started at 2010-03-12 10:19:38 +0100.]
2010-03-12 10:19:40.159 Plist2[10897:20b] dict didn't exist, creating...
2010-03-12 10:19:40.180 Plist2[10897:20b] writing to /Users/henrik/Library/Application Support/iPhone Simulator/User/Applications/5B1E8FF3-45D6-443B-BB0B-2073EAE22520/Documents/foo.plist...
2010-03-12 10:19:40.191 Plist2[10897:20b] dumping...
2010-03-12 10:19:40.198 Plist2[10897:20b] key=key1, value=1
2010-03-12 10:19:40.199 Plist2[10897:20b] key2 didn't exist, adding...
2010-03-12 10:19:40.199 Plist2[10897:20b] dumping...
2010-03-12 10:19:40.200 Plist2[10897:20b] key=key1, value=1
2010-03-12 10:19:40.202 Plist2[10897:20b] key=key2, value=default-2

Tuesday, September 1, 2009

iphone tutorial: Memory management - autorelease and NIB files

In this tutorial we're going to dive a bit deeper into Cocoa memory management, specifically looking at a feature called autorelease. If you haven't read the first part of the memory management tutorial, it's highly recommended that you do.


Cocoa memory ownership rules


In the first part of this tutorial we introduced the concept of memory ownership. To help deciding who actually owns a memory block, the Cocoa designers have developed a set of rules that all Cocoa classes and applications should follow. This set of rules can be found in the "Memory Management Programming Guide for Cocoa":


---

To make sure it is clear when you own an object and when you do not, and what responsibilities you have as an owner, Cocoa sets the following policy:


You own any object you create.

You “create” an object using a method whose name begins with “alloc” or “new” or contains “copy” (for example, alloc, newObject, or mutableCopy).


If you own an object, you are responsible for relinquishing ownership when you have finished with it. You relinquish ownership of an object by sending it a release message or an autorelease message (autorelease is discussed in more detail in “Delayed Release”). In Cocoa terminology, relinquishing ownership of an object is typically referred to as “releasing” an object.


If you do not own an object, you must not release it.

---


If you think these rules are too complicated to remember, try to at least remember the naming scheme; methods beginning with "alloc" or "new" or contains "copy" creates an object (and returns a reference to that object) that you are responsible for freeing. That is, these methods, transfer the ownership to you, the caller of the moethod.


A new concept is also introduced; autorelease or "delayed release". If you call 'autorelease' on an object reference, that object won't be released immediately, but "later". Before we explain what later means, it might be good to understand why there is such a feature as a "delayed release".


Autorelease


Why would you want to release an object later instead of now or not at all? One example is when you have a method that creates objects for someone else than itself.


(MyClass.m)

+(MyClass *)createObject {

MyClass *reference = [[MyClass alloc] init];

return reference;

}


The method 'createObject' in the class MyClass allocates and initialises a MyClass object and then returns the reference to the newly created object. An application calling this method


(MyApplication.m)

MyClass *myClass = [MyClass createObject];


would not be responsible for releasing the object since the 'createObject' method name doesn't contain any of the "magic words" listed in the rules above. The 'createObject' method on the other hand calls 'alloc' which is a magic word and is therefore responsible for calling release.


It's impossible to call 'release' on the created object after the return statement and 'release' is called before the return statement, the object will be deallocated and the method will return a reference to a deallocated object which will lead to chaos. So when should 'release' be called? Well, "later"...


Here "later" means "after the return statement" and late enough so that the caller of the method gets a chance to store the returned release in a variable (or somewhere else) and call 'retain' on it.


This is where the autorelease mechanism steps in and provides a solution. If the method creating the object calls 'autorelease' on the reference instead of 'release' evertyhing will work; the method has fulfilled its responsibility to release the object it created and the caller of the method gets a chance to call retain on the returned object.


(MyClass.m)

+(MyClass *)createObject {

MyClass *reference = [[MyClass alloc] init];

[reference autorelease]; // call autorelease on the newly create object

return reference;

}


(MyApplication.m)

MyClass *myClass = [MyClass createObject];

[myClass retain]; // call retain on the returned object to protect it from being deallocated


The autorelease method can be called anywhere the release method should have been called but just as with release it's important to call it the right number of times; for each time 'autorelease' is called 'release' will be called "later".


NIB files


We mentioned above that a method that creates an object for someone else is a typical user of the autorelease functionality. Another user of autorelease is a NIB file, or actually the method that load it. A NIB file contains a graph of objects where most objects are connected to each other using outlets. When the loader method rebuilds this object graph in memory, using the NIB file as a blueprint, it creates all objects with a reference count of 1 and then autorelease them.


When the connections between the objects are re-established, the loader method calls setter methods of the objects, which should retain the object reference if they want to prevent the newly loaded object from being deallocated. Ownership is transferred to these objects, meaning that the objects also are responsible for releasing the references in their 'dealloc' method. Setter methods for outlets are normally synthesized using the @property/@synthesize mechanism of Objective C.


@property (nonatomic, retain) IBOutlet someClass *someOutlet;


The 'retain' keyword of the property declaration will ensure that the corresponding setter method created by the @synthesize keyword will retain the reference passed to it.


However, there are also top-level objects in the file that have no natural owner. If you want to keep those objects around, which you normally do, you have to manually retain them (and then later manually releasing them). But how do you get hold of the references to them if they have no "owners"? Well, they do actually get a temporary owner during the loading process; references to all the top-level objects are stored in an array, returned by the 'loadNibNamed:owner:options' method.


As a simple alternative to iterating through the array and retain all the objects in it individually you could retain the array itself instead (and then later release it). This works since it prevents the array from being (auto) released and deallocated - if it was it would release all the references stored in it (decreasing the reference count to zero) and thus force all the objects in the array to be deallocated.


NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:@"Nib1" owner:self options:nil];

if ( nibArray == nil ) {

NSLog(@"loadNibNamed failed");

return;

}

[nibArray retain];


NIB files with "unconnected" top-level objects aren't that common since it would be useless to define a lot of objects in a NIB that no one uses. Much more common is that the top-level objects are connected to outlets in the "File's owner" object. When that is the case you don't have to worry about implicitly retaining the objects since they will be retained by the setter methods in the "File's owner".


Friday, May 29, 2009

iPhone tutorial: Creating table cells in Interface Builder





Creating complex table view cells programmatically can be quite tedious. In fact, so tedious that it can affect your creativity negatively. Thankfully, it is possible to design table view cells in Interface Builder (IB) and then use them in your application and that is what we're going to explore in this tutorial.

While designing table view cells in IB is quite simple and intuitive, using them in an application is far from intuitive. Especially if you want to use   a single table view cell for multiple (all?) rows in a table. The problem with using a cell multiple times is that you have to create multiple instances of the UITableViewCell object, which in turn means that you have to load the xib containing the object multiple times. This means that you have to create a reusable IB file (xib/nib).

How many instances do you need to create? Well, that is basically decided by the number of visible rows in the table view. Each visible row needs its own instance of a corresponding UITableViewCell object. The cell reuse scheme really won't kick in until you start scrolling the table view, so if you have a table view with 10 visible rows and 20 total rows, you will normally have to create 10 instances of the cell object. This is nothing you should rely on or try to exploit since it's the UITableView object that decides exactly how many instances you need of a specific cell. It does this by returning nil when you call 'dequeueReusableCellWIthIdentifier', which basically is an order to create a new instance - simple as that.

This tutorial could be seen as "part 4" of the "UITableView from the ground up", but I decided against it since it is more or less independent - focusing just on how to load and reuse UITableViewCell objects from an IB file. Therefore, we're going to create a new project instead of modifying the one we created in part 1.

Create the project

Start XCode and choose "File/New Project" from the menu to create a "Window-Based Application" and name it "TableCellLoader". We're going to create a few classes right away, so  select the Classes-group in the "Groups & Files" panel i XCode. Then choose "File/New File" from the menu and create a NSObject subclass called "CellOwner.m" (remember to check the "Also create h-file" checkbox). After that, choose "File/New File" again and create a UITableViewCell subclass called "Cell1" and again to create yet another UITableViewCell subclass, this time called "Cell2".

Classes/Cell1.h

The UITableViewCell we're going to create in IB will be represented by an UITableViewCell subclass in our application. We're actually going to create two similar cells in IB - "Cell1" and "Cell2" - which will only differ in regards to the layout of their contents. The cell content is very simple - two UILabels which will allow us to display two strings. Since the cells are so similiar Cell2.h will be identical toll Cell1.h - with the exception of the name of the class.

In order to access the two labels in the table view cell, we need to define two instance variables - "label" and "label2" - containing pointers to UILabel objects. Since we'll be manipulating them from IB, we also need to make them into properties and mark them with IBOutlet. Those changes should result in the following:

@interface Cell1 : UITableViewCell {
UILabel *label;
UILabel *label2;
}

@property (nonatomic, retain) IBOutlet UILabel *label;
@property (nonatomic, retain) IBOutlet UILabel *label2;

Classes/Cell1.m

Since we created this file as a subclass of UITableViewCell it will already contain some code, but ignore that for now since all we need to do right now is to synthesize the properties we created in the h-file. Since As we mentioned above "Cell1" and "Cell2" are very similar so apply the same changes to the Cell2.m.

All we need to do is to add two @synthesize statements right after the @implementation statement:

@implementation Cell1
@synthesize label;
@synthesize label2;

Classes/CellOwner.h

The CellOwner class will be used to load UITableViewCell objects from IB (xib/nib) files and the rather strange name was chosen because the CellOwner class will be set as the "File's owner" in the IB files for the UITableViewCell objects ("Cell1" and "Cell2").

Since this class is just some kind of "support" class for the IB object loading procedure it doesn't contain much or do much. All it contains is a pointer to the UITableViewCell subclass that is loaded from the IB file and a method which loads an IB file. Therefore the h-file will be quite simple:

@interface CellOwner : NSObject {
UITableViewCell *cell;
}

@property (nonatomic, retain) IBOutlet UITableViewCell *cell;

- (BOOL)loadMyNibFile:(NSString *)nibName;

Classes/CellOwner.m

Since we defined a property in the h-file, we - as always - need to add a corresponding @synthesize statement in the m-file, so add the following right after the @implementation statement:

@synthesize cell;

In the h-file we also declared a method which we need to implement in the m-file, so add the following:

- (BOOL)loadMyNibFile:(NSString *)nibName {
    // The myNib file must be in the bundle that defines self's class.
    if ([[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil] == nil)
    {
        NSLog(@"Warning! Could not load %@ file.\n", nibName);
        return NO;
    }
    return YES;
}

The source code for this method was taking more or less directly from an example in the "Resource Programming Guide" which you can find by searching for "loadMyNibFile" in the API docs in XCode (remember to select "Full-Text" in the upper left corner of the API docs window.

As you can see, it's quite simple to load an IB file - it's basically just one line of code! The rest of the code is error handling. To keep up the pace of this tutorial we won't dive into the details of the NSBundle class, so if you want to know more about that right now, please search for it in the API docs.

'loadNibNamed' takes three arguments; the name of the nib (IB) file to load, the owner of the file ("File's owner" in IB) and something called 'options'. As we said above, our single CellOwner object will be the "File's owner" of all the cells we load, which explains why we pass 'self' as the value of the 'owner' argument. The 'options' argument is only used if the IB file we load contain any non-standard "proxy objects". We don't use this feature and thus we can pass 'nil' as the value.

Classes/TableCellLoaderAppDelegate.h

Our application delegate will contain a reference to an object of the CellOwner class we created above, so add an instance variable and a property for it as well as marking it as IBOutlet since we'll create it in IB. After you're done, the file should look like this:

@interface TableCellLoaderAppDelegate : NSObject {
   UIWindow *window;
   CellOwner *cellOwner;
}

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

Classes/TableCellLoaderAppDelegate.m

This is the file we'll keep adding code to during the tutorial but right now we'll just add the @synthesize statement corresponding to the property we added in the h-file ("cellOwner"). So add the following just below the already existing "@synthesize window" statement:

@synthesize cellOwner;

Test build

Build the project in XCode (CMD-B) to verify that everything works. There should be no warnings or errors reported.

Resources/MainWindow.xib

Double-click on Resources/MainWindow.xib to start IB and load the file. As always, remember to switch to "hierarchical view mode" by pressing the middle button above the "View Mode" text in the upper left corner of the MainWindow.xib window.

We need a table view in order to be able to experiment with table view cells, so let's add one to our window. Open the Library window in IB (CMD-L) and drag a "Table View" object from the "Data Views" section and drop it onto the Window object in the MainWindow.xib window.

A table view needs the help of two other objects to function properly - a 'delegate' and a 'dataSource' - so we'll need to connect those outlets of the the "Table View" object we just added. CTRL-drag from "Table View" to "Table Cell Loader App Delegate" and choose 'delegate' from the window that pops up. Repeat the process for the 'dataSource'.

We're going to use a single instance of our "CellOwner" object to load our UITableViewCell objects from IB files, so let's create that one as well. Drag a "Object" object from the "Controllers" section of the Library window (CMD-L) and drop it at the end of the list in the MainWindow.xib window. 

Select the "Object" object in MainWindow.xib and press CMD-4 to bring the Inspector window to the front and select the Identity tab. Here you should change the class of the object to "CellOwner" in the drop-down list. Remember that we added a "cellOwner" property to our application delegate? Now is the time to connect it, so CTRL-drag from "Table Cell Loader App Delegate" to "Cell Owner" in MainWindow.xib and choose "cellOwner" in the pop-up window that appears.

We're done with MainWindow.xib so save the file by pressing CMD-S.

Resources/Cell1.xib

Now it's time to create our custom UITableViewCell objects in IB, so choose "File/New" in the menu and select the "Empty" template. Select the new window that appears ("Untitled") and choose "File/Save As" from the menu. Ensure that you're in the "TableCellLoader" directory and then save the file as "Cell1". IB will ask you if you want to add the file to the project, which we do so check the checkbox and press "Add".

This file should contain the first of our UITableViewCell object, so drag a  "Table View Cell" from the "Data Views" section of the Library window (CMD-L) into the Cell1 window. The cells we're creating are UITableViewCell subclasses, so the first thing we need to do is set the class of the "Table View Cell" object by selecting it, pressing CMD-4 and select "Cell1" from the drop-down menu.

As we mentioned earlier our table view cells should contain two UILabel objects accessible through the 'label' and 'label2' properties/outlets of our Cell1 class, so let's create them. Since we want to layout the UILabel objects in a specific way we should bring up the "design window" of the "Cell1" object by double-clicking on it.

When doing this, a small table view cell shaped window should appear. Notice that the cell by default has a blue "disclosure button" to the right? We're going to use it in our tutorial so we'll keep it, but it's no problem deleting it if you want to.

Drag a "Label" object from the "Inputs & Values" section of the Library window (CMD-L) into the design window of the Cell1 object and place it to the far left in the dashed rectangle. Drag another "Label" object from the Library window and place it to the far right in the dashed rectangle (see screenshot).

In order to be able to access these labels from our applications we need to connect them to the 'label' and 'label2' outlets we created in the Cell1.h file. To do this, CTRL-drag from the "Cell1" object in the Cell1 window to the leftmost label object in the "Cell1" design window and select the 'label' outlet in the window that pops up. Repeat the process to connect the rightmost label to the 'label2' outlet.

As we have mentioned in earlier tutorials, the table view tries to reuse its cells as a way to optimise its performance. The rationale behind this is that if object creation is kept to a minimun the performance will increase. In order for this reuse scheme to work, each "type" of cell in the table needs to be assigned a "reuse identifier". If there are two types of cells in a table there are only two different identifiers, even if the total amount of cells (rows) is much larger. The UITableViewCell property which specifies the "reuse identifier" is called 'reuseIdentifier' but here in IB it's just called "Identifier", which you can see if you select the "Cell1" object and press CMD-1. Enter "Cell1" in the Identifier field.

Now it's time to configure the "File's Owner" object. Start by changing the class to "CellOwner" since we previosly explained that "CellOwner" will be the owner of all our IB created cells. Do this by selecting "File's Onwer", press CMD-4 and choose "CellOwner" from the drop-down menu.

Once the class is set to "CellOwner" we can connect the 'cell' outlet of the "File's Owner" object to the "Cell1" object. Do this by CTRL-dragging from "File's owner" to "Cell1" and choose the 'cell' outlet from the window that pops up.

We're done with this file now, so save it by pressing CMD-S.

Resources/Cell2.xib

Cell2.xib is almost identical to Cell1.xib so repeat all the steps from above but lay out the UILabels a bit differently so it's possible to discern between the two cells. I chose to place the first label slightly to the left of the center of the dashed area and the second label slightly to the right instead of to the left and right extremes (see screenshot).

When you're done with all the connections, class changes, etc. remember to save the file by pressing CMD-S. 

A nice way of seeing if all the files in IB are saved is to activate the "Window" in IB and look at the list of window names at the end of the menu. Unsaved windows have a small dot the left of the name, so if you've save all windows you should see no dots.

Classes/TableCellLoaderAppDelegate.m

Now it's time to return to XCode to implement the required methods of the UITableViewDelegate and UITableViewDataSource protocols since we connected the 'delegate' and 'dataSource' outlets of our "Table View" object in IB to the "Table Cell Loader App Delegate" object which is implemented in TableCellLoaderAppDelegate.m.

We're going to work with the Cell1 and Cell2 classes so start by importing the corresponding h-files by adding the following right after the already existing #import statement:

#import "Cell1.h"
#import "Cell2.h"

After that we should configure the number of rows in our table by adding the following just below the already present 'dealloc' method:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 20;
}

Finally, we're coming to the really interesting part of this tutorial - how to provide our custom made, IB designed UITableViewCell objects to the table view. We do this adding a quite impressive 'cellForRowAtIndexPath' method just below the 'numberOfRowsInSection' method.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// init the return value to nil
UITableViewCell *cell = nil;
if ( (indexPath.row & 1) == 0 ) {
// "even rows", that is, row 0, row 2, row 4, etc.
// check if the table view has a cell of the appropriate type we can reuse
Cell1 *cell1 = (Cell1 *)[tableView dequeueReusableCellWithIdentifier:@"Cell1"];
if ( cell1 != nil ) {
// yes it had a cell we could reuse
NSLog(@"reusing cell '%@' (%p) for row %d...", cell1.reuseIdentifier, cell1, indexPath.row);
} else {
// no cell to reuse, we have to create a new instance by loading it from the IB file
NSString *nibName = @"Cell1";
[cellOwner loadMyNibFile:nibName];
// get a pointer to the loaded cell from the cellOwner and cast it to the appropriate type
cell1 = (Cell1 *)cellOwner.cell;
NSLog(@"Loading cell from nib %@", nibName);
}
// set the labels to the appropriate text for this row
cell1.label.text = [NSString stringWithFormat:@"this is..."];
cell1.label2.text = [NSString stringWithFormat:@"...row %d", indexPath.row];
cell = cell1;
} else {
// "odd rows", that is, row 1, row 3, row 5, etc.
// check if the table view has a cell of the appropriate type we can reuse
Cell2 *cell2 = (Cell2 *)[tableView dequeueReusableCellWithIdentifier:@"Cell2"];
if ( cell2 != nil ) {
// yes it had a cell we could reuse
NSLog(@"reusing cell '%@' (%p) for row %d...", cell2.reuseIdentifier, cell2, indexPath.row);
} else {
// no cell to reuse, we have to create a new instance by loading it from the IB file
NSString *nibName = @"Cell2";
[cellOwner loadMyNibFile:nibName];
// get a pointer to the loaded cell from the cellOwner and cast it to the appropriate type
cell2 = (Cell2 *)cellOwner.cell;
NSLog(@"Loading cell from nib %@", nibName);
}
// set the labels to the appropriate text for this row
cell2.label.text = [NSString stringWithFormat:@"this is..."];
cell2.label2.text = [NSString stringWithFormat:@"...row %d", indexPath.row];
cell = cell2;
}

// return the cell which will be either a "Cell1" or "Cell2" object.
return cell;
}

I have tried to explain what's going on in the inlined comments so I won't bore you with repeating all that here in the text. Instead I think we're more than ready to see some results - yeah, it's time for a test run!

Test run

Build and run the project in XCode by pressing CMD-Return and once the simulator have started you should see a table where the "even rows" have one appearance and the "odd rows" another. Try to scroll down until you get to row 19 where the table ends. If you switch back to XCode and bring the console window to the front (CMD-R) you should see something like this.

2009-05-29 09:56:37.479 TableCellLoader[14364:20b] Loading cell from nib Cell1 for row 10...
2009-05-29 09:56:37.488 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 9...
2009-05-29 09:56:37.491 TableCellLoader[14364:20b] Loading cell from nib Cell1 for row 8...
2009-05-29 09:56:37.493 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 7...
2009-05-29 09:56:37.497 TableCellLoader[14364:20b] Loading cell from nib Cell1 for row 6...
2009-05-29 09:56:37.501 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 5...
2009-05-29 09:56:37.508 TableCellLoader[14364:20b] Loading cell from nib Cell1 for row 4...
2009-05-29 09:56:37.513 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 3...
2009-05-29 09:56:37.522 TableCellLoader[14364:20b] Loading cell from nib Cell1 for row 2...
2009-05-29 09:56:37.525 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 1...
2009-05-29 09:56:37.527 TableCellLoader[14364:20b] Loading cell from nib Cell1 for row 0...
2009-05-29 09:56:39.434 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 11...
2009-05-29 09:56:39.514 TableCellLoader[14364:20b] reusing cell 'Cell1' (0x52d780) for row 12...
2009-05-29 09:56:39.610 TableCellLoader[14364:20b] reusing cell 'Cell2' (0x52d200) for row 13...
2009-05-29 09:56:40.001 TableCellLoader[14364:20b] reusing cell 'Cell1' (0x52c870) for row 14...
2009-05-29 09:56:40.082 TableCellLoader[14364:20b] reusing cell 'Cell2' (0x52c360) for row 15...
2009-05-29 09:56:40.154 TableCellLoader[14364:20b] reusing cell 'Cell1' (0x52c0e0) for row 16...
2009-05-29 09:56:40.482 TableCellLoader[14364:20b] reusing cell 'Cell2' (0x52bb60) for row 17...
2009-05-29 09:56:40.543 TableCellLoader[14364:20b] reusing cell 'Cell1' (0x52b610) for row 18...
2009-05-29 09:56:40.576 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 19...
2009-05-29 09:56:42.298 TableCellLoader[14364:20b] reusing cell 'Cell1' (0x528fd0) for row 10...
2009-05-29 09:56:42.448 TableCellLoader[14364:20b] reusing cell 'Cell2' (0x526280) for row 9...

As we have seen in earlier tutorials, no reusal of cell is going on for the first rows since they all are visible and every visible row needs its own UITableViewCell instance. Once we start scrolling, the reuse scheme kicks into effect though. It's not just the first rows that have their own instances though. As you can see row 19 was also loaded/created from the IB file (nib).

Adding interaction

If you want to interact with the cells (detecting row selection or "disclosure button" touches) you can add the following to TableCellLoaderAppDelegate.m:

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
NSLog(@"accessoryButtonTappedForRowWithIndexPath: row=%d", indexPath.row);
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"didSelectRowAtIndexPath: row=%d", indexPath.row);
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

This won't do anything else but log some messages in the XCode console window, but it opens up a world of possibilities. It also demonstrates that the "disclosure button" added by default by IB works right out of the box.

Summary

There are many ways of using Interface Builder created table cells in your application but the procedure I have presented here in this tutorial is quite simple to understand, at least for a novice iPhone developer like myself . Remember that things I write about in this blogs are things that I have recently began understanding myself! That is, I am no expert and don't claim to present the best, or even correct, way of doing things. What I'm trying to say is that I welcome all kinds of comments! ;)

Tuesday, May 26, 2009

iPhone tutorial: UITableView from the ground up, part 3

Now that we understand the really basic stuff about the UITableView and UITableViewCell classes, we're ready to move on to some simple interaction. We'll continue modifying the Table1 project from part 1 and part 2 so please check those parts out before reading any further.

Classes/Table1AppDelegate.m

Modify the 'cellForRowAtIndexPath' method to make it look like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

// try to retrieve "cell 1" from the UITableView cache
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell 1"];
if ( cell == nil ) {
// "cell 1" wasn't present in the cache, so create it
cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"cell 1"];
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
NSLog(@"creating cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);
} else {
// "cell 1" was present in the cache, so log that we're reusing it
NSLog(@"reusing cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);
}
  
cell.text = [NSString stringWithFormat:@"this is row %d", indexPath.row];
return cell;
}

The only new thing here really is the 

cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;

row which says that we want a standard "detail disclosure" button to the right of the text in our cell. This is the standard way in the iPhone user interface to indicate that touching this cell will take you to a new screen. Just by adding this you also instruct the table view to start sending 'accessoryButtonTappedForRowWithIndexPath' to its 'delegate' object.

In order to receive those messages we have to implement the appropriate method, so add the following to the end of the file.

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
NSLog(@"accessoryButtonTappedForRowWithIndexPath: row=%d", indexPath.row);
}

A short note on the use of indexPath in combination with table views

You might have noticed that a lot of the delegate methods for the table view have an 'indexPath' argument of type NSIndexPath. I wrote quite a lot about index paths in a previous post so have a look there if you want a full explanation. Here I will just mention that the index paths used in conjunction with table views have two levels which represent the section and the row indices of the table. As an alternative solution two arguments 'section' and 'row' could have been used instead of the 'indexPath' argument. If the table only has one section all you need to care about is the row index, which you read from the indexPath.row property. 

Test run

Build and run the project in XCode (CMD-Return), wait for the simulator to start and then go back to XCode to bring the XCode console window to the front (CMD-R). Touch a few disclosure buttons and you should see something like this in the console window:

009-05-26 17:07:24.807 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=0
2009-05-26 17:07:27.655 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=1
2009-05-26 17:07:29.471 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=2
2009-05-26 17:07:30.943 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=3
2009-05-26 17:07:35.919 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=8

What you do with these messages is completely up to you, but a very common way of using this information is to switch to another table view which shows information "on the next level" in some kind of hierarchical data. You often see this when table views are used together with navigation controllers since they offer a very easy way of switching to a new view as well as providing a way to get back to the last one (the "back button").

Classes/Table1AppDelegate.m

Another way of adding interaction to a table is by detecting "selections", that is detecting that the user touched a row/cell in the table. Above we detected that the user touched the "disclosure button", but here we'll detect touches to anywhere outside of the disclosure button. Add the following to the end of the file.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"didSelectRowAtIndexPath: row=%d", indexPath.row);
}

Test run

Build and run the project and touch a few rows once the simulator has started. You should see something like this in the XCode console window:

2009-05-26 17:12:00.289 Table1-2[11530:20b] didSelectRowAtIndexPath: row=1
2009-05-26 17:12:02.770 Table1-2[11530:20b] didSelectRowAtIndexPath: row=2
2009-05-26 17:12:03.882 Table1-2[11530:20b] didSelectRowAtIndexPath: row=5
2009-05-26 17:12:05.322 Table1-2[11530:20b] didSelectRowAtIndexPath: row=8

If you are really observant you probably noticed that the row you touched stays selected (as indicated by the blue colour). This is because it is our responsibility to deselect it, which is accomplished by calling 'deselectRowAtIndexPath':

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"didSelectRowAtIndexPath: row=%d", indexPath.row);
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

Summary

Allowing the user to interact with the table view opens up for a lot of possibilities and it is nice to know that the detection of user interactions is as easy as this. At least for simple interactions.

Sunday, May 24, 2009

iPhone tutorial: UITableView from the ground up, part 2

In part 1 of this tutorial we created a simple table view with just a few lines of code to show that, even though table views are a bit intimidating, they are possible to fully understand if you take small enough steps. This part of the tutorial is going to continue from where the last part ended, so if you haven't already created the "Table1" XCode project you will have to work through part 1 first.

Classes/Table1AppDelegate.m

We're going to start by exploring how to reuse cells, since this is something Apple more or less recommends us to do. The first hint Apple gives us is that there is only one way to initialise a UITableViewCell and that involves calling 'initWithFrame:reuseIdentifier' which takes a "reuse identifier" as the second argument. You can set this to nil - as we did in part 1 - if you really don't want to reuse the cell, but that will probably affect the performance of (large) table views significally.

The table view calls it's 'dataSource' delegate whenever it needs a cell for a specific row since it has no clue of how a specific cell (row) should look like (background colour, detail disclosure buttons, etc.) or what it should contain (text, images, etc.). However, if you  assign a "reuse identifier" (name) to the cell you provide to the table view, the table view will try to cache the cell for you. This means that the table view tries to store the cells created by the "data source" for later use so that the data source doesn' t have to create a new cell everytime - sometimes it will be able to reuse an existing cell.

The UITableView class has a method called 'dequeueReuseableCellWithIdentifier' which is used to retrieve a UITableViewCell object from the UITableView cache, if present. If the cell isn't present, it returns nil. Let's modify our 'tableView:cellForRowAtIndexPath' method to make use of this reuse-scheme. We'll call our cell "cell1" in the example below:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

// try to retrieve "cell1" from the UITableView cache
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell1"];
if ( cell == nil ) {
// "cell 1" wasn't present in the cache, so create it
NSLog(@"creating cell for row %d...", indexPath.row);
cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"cell1"];
} else {
// "cell 1" was present in the cache, so log that we're reusing it
NSLog(@"reusing cell for row %d...", indexPath.row);
}

if ( indexPath.row == 0 ) { 
// assign a text to row 0
cell.text = [NSString stringWithFormat:@"this is row %d", indexPath.row];
}
return cell;
}

The comments in the source code above tries to explain what we're doing, so I won't elaborate on that. One thing is worth mentioning though and that is the 'indexPath.row' statement. If you look in the API docs for NSIndexPath you won't see any 'row' property. This is because "UITableView declares a category on NSIndexPath that enables you to get the represented row index (row property)", as can be read in the API docs for the UITableViewDelegate protocol. The "category" that is mentioned above is an Obejctive C feature which allows you to add methods to a class without actually subclassing it.

Exploring the reuse scheme

If you compile and run (CMD-Return) in XCode and wait for the simulator to start, you'll see a table with the text "this is row 0" in the first row. Apart from that it is empty, but you are still able to select rows 1 and 2 as well. If you check the console window (CMD-R) in XCode, you'll see somthing like this:

2009-05-24 18:29:06.375 Table1-2[8618:20b] creating cell for row 2...
2009-05-24 18:29:06.387 Table1-2[8618:20b] creating cell for row 1...
2009-05-24 18:29:06.394 Table1-2[8618:20b] creating cell for row 0...

The first thing to notice is that UITableView seems to have requested cells in the "wrong order", that is starting with row 2 instead of row 0. This is of course nothing you should take advantage of, but it's interesting to note. The really interesting thing to note, though, is that none of the cells seems to have been reused, even though we initialised all our cells with the same reuse identfier "cell1". What's going on, is the reuse mechanism broken or what?

Adding more rows

Let's see what happens if we change the number of rows in our table by editing 'tableView:numberOfRowsInSection' to make it return 10 instead of 3.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 10;
}

Build and run (CMD-Return), wait for the simulator to start and then bring the XCode console window to the front (CMD-R). Now scroll the table view by "moving your finger upwards" on the simulator and you should see something like this in the console window:

2009-05-24 18:44:49.417 Table1-2[8663:20b] creating cell for row 9...
2009-05-24 18:44:49.425 Table1-2[8663:20b] creating cell for row 8...
2009-05-24 18:44:49.426 Table1-2[8663:20b] creating cell for row 7...
2009-05-24 18:44:49.427 Table1-2[8663:20b] creating cell for row 6...
2009-05-24 18:44:49.431 Table1-2[8663:20b] creating cell for row 5...
2009-05-24 18:44:49.434 Table1-2[8663:20b] creating cell for row 4...
2009-05-24 18:44:49.435 Table1-2[8663:20b] creating cell for row 3...
2009-05-24 18:44:49.435 Table1-2[8663:20b] creating cell for row 2...
2009-05-24 18:44:49.436 Table1-2[8663:20b] creating cell for row 1...
2009-05-24 18:44:49.436 Table1-2[8663:20b] creating cell for row 0...
2009-05-24 18:45:25.892 Table1-2[8663:20b] reusing cell for row 1...
2009-05-24 18:45:25.975 Table1-2[8663:20b] reusing cell for row 0...
2009-05-24 18:45:39.922 Table1-2[8663:20b] reusing cell for row 0...
2009-05-24 18:45:41.884 Table1-2[8663:20b] reusing cell for row 0...
2009-05-24 18:45:42.053 Table1-2[8663:20b] reusing cell for row 1...
2009-05-24 18:45:42.187 Table1-2[8663:20b] reusing cell for row 0...
2009-05-24 18:45:43.552 Table1-2[8663:20b] reusing cell for row 1...
2009-05-24 18:45:43.566 Table1-2[8663:20b] reusing cell for row 0...

As you can see, the 10 first rows are created without reusing any cells, but as soon as you start scrolling the reuse mechanism seems to kick in. Everytime row 0, 1 and 2 "reappears" the cells seem to be reused.

Adding even more rows

Wonder what happens if 'tableView:numberOfRowsInSection' returns 100 instead of 10? Edit the method, build and run (CMD-Return), wait for the simulator to start, bring the XCode console window to the front (CMD-R), start scrolling around and you should see somthing like this:

2009-05-24 18:54:22.903 Table1-2[8704:20b] creating cell for row 10...
2009-05-24 18:54:22.926 Table1-2[8704:20b] creating cell for row 9...
2009-05-24 18:54:22.932 Table1-2[8704:20b] creating cell for row 8...
2009-05-24 18:54:22.939 Table1-2[8704:20b] creating cell for row 7...
2009-05-24 18:54:22.947 Table1-2[8704:20b] creating cell for row 6...
2009-05-24 18:54:22.951 Table1-2[8704:20b] creating cell for row 5...
2009-05-24 18:54:22.953 Table1-2[8704:20b] creating cell for row 4...
2009-05-24 18:54:22.955 Table1-2[8704:20b] creating cell for row 3...
2009-05-24 18:54:22.962 Table1-2[8704:20b] creating cell for row 2...
2009-05-24 18:54:22.964 Table1-2[8704:20b] creating cell for row 1...
2009-05-24 18:54:22.968 Table1-2[8704:20b] creating cell for row 0...
2009-05-24 18:54:38.025 Table1-2[8704:20b] reusing cell for row 10...
2009-05-24 18:54:40.375 Table1-2[8704:20b] creating cell for row 11...
2009-05-24 18:54:40.526 Table1-2[8704:20b] reusing cell for row 12...
2009-05-24 18:54:41.365 Table1-2[8704:20b] reusing cell for row 13...
2009-05-24 18:54:42.043 Table1-2[8704:20b] reusing cell for row 14...
2009-05-24 18:54:42.136 Table1-2[8704:20b] reusing cell for row 15...
2009-05-24 18:54:42.693 Table1-2[8704:20b] reusing cell for row 16...
2009-05-24 18:54:42.857 Table1-2[8704:20b] reusing cell for row 17...
2009-05-24 18:54:43.040 Table1-2[8704:20b] reusing cell for row 18...
2009-05-24 18:54:43.374 Table1-2[8704:20b] reusing cell for row 19...

Wow, now there is a lot of reusing going on! It seems as if the cells that appears for the first time when scrolling also are resued. That's why you see that rows 12 to 19 are reused. If you're really observant, you'll also notice something strange in the simulator. The text "this is row 0" appears in more than one place! What a nasty bug!! Hey, take it easy, it might not be a bug, just some proof of that the cell really is reused.

Insights into the reuse scheme

If the text "this is row 0" had appeared for all reused cells, I would have been able to explain this, but now I don't really understand it. I'm guessing it has to do with the fact that we're creating cells for row 0 to 11 with the same identifier. After that we start reusing cells and since there are more than one cell with the same identifier we sometimes get the row-0 instance, sometime the row-1 instance, and so on up to row-11 instance. After that we get the row-0 instance again, and so on.

If we modify the 'tableView:cellForRowAtIndexPath' to make output the cell 'reuseIdentifier' property as well as the pointer to the object we see that this guess might be true.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

// try to retrieve "cell1" from the UITableView cache
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell1"];
if ( cell == nil ) {
// "cell 1" wasn't present in the cache, so create it
cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"cell1"];
NSLog(@"creating cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);
} else {
// "cell 1" was present in the cache, so log that we're reusing it
NSLog(@"reusing cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);
}

if ( indexPath.row == 0 ) { 
// assign a text to row 0
cell.text = [NSString stringWithFormat:@"this is row %d", indexPath.row];
}
return cell;
}

If you run the modified version you'll see somthing like this:

2009-05-24 20:12:51.907 Table1-2[8919:20b] creating cell 'cell1' (0x5284c0) for row 10...
2009-05-24 20:12:51.917 Table1-2[8919:20b] creating cell 'cell1' (0x5293c0) for row 9...
2009-05-24 20:12:51.929 Table1-2[8919:20b] creating cell 'cell1' (0x5287e0) for row 8...
2009-05-24 20:12:51.930 Table1-2[8919:20b] creating cell 'cell1' (0x526560) for row 7...
2009-05-24 20:12:51.931 Table1-2[8919:20b] creating cell 'cell1' (0x5292f0) for row 6...
2009-05-24 20:12:51.934 Table1-2[8919:20b] creating cell 'cell1' (0x528940) for row 5...
2009-05-24 20:12:51.937 Table1-2[8919:20b] creating cell 'cell1' (0x5297d0) for row 4...
2009-05-24 20:12:51.938 Table1-2[8919:20b] creating cell 'cell1' (0x520970) for row 3...
2009-05-24 20:12:51.939 Table1-2[8919:20b] creating cell 'cell1' (0x529940) for row 2...
2009-05-24 20:12:51.940 Table1-2[8919:20b] creating cell 'cell1' (0x529a60) for row 1...
2009-05-24 20:12:51.941 Table1-2[8919:20b] creating cell 'cell1' (0x529be0) for row 0...
2009-05-24 20:13:08.380 Table1-2[8919:20b] creating cell 'cell1' (0x52e290) for row 11...
2009-05-24 20:13:09.190 Table1-2[8919:20b] reusing cell 'cell1' (0x529be0) for row 12...
2009-05-24 20:13:09.517 Table1-2[8919:20b] reusing cell 'cell1' (0x529a60) for row 13...
2009-05-24 20:13:52.790 Table1-2[8919:20b] reusing cell 'cell1' (0x529940) for row 2...
2009-05-24 20:13:53.415 Table1-2[8919:20b] reusing cell 'cell1' (0x529940) for row 14...
2009-05-24 20:13:53.511 Table1-2[8919:20b] reusing cell 'cell1' (0x520970) for row 15...
2009-05-24 20:13:54.068 Table1-2[8919:20b] reusing cell 'cell1' (0x5297d0) for row 16...
2009-05-24 20:13:54.106 Table1-2[8919:20b] reusing cell 'cell1' (0x528940) for row 17...
2009-05-24 20:13:54.568 Table1-2[8919:20b] reusing cell 'cell1' (0x5292f0) for row 18...
2009-05-24 20:13:54.635 Table1-2[8919:20b] reusing cell 'cell1' (0x526560) for row 19...
2009-05-24 20:13:54.652 Table1-2[8919:20b] reusing cell 'cell1' (0x5287e0) for row 20...
2009-05-24 20:13:54.710 Table1-2[8919:20b] reusing cell 'cell1' (0x5293c0) for row 21...
2009-05-24 20:13:54.743 Table1-2[8919:20b] reusing cell 'cell1' (0x5284c0) for row 22...
2009-05-24 20:13:54.776 Table1-2[8919:20b] reusing cell 'cell1' (0x52e290) for row 23...
2009-05-24 20:13:54.810 Table1-2[8919:20b] reusing cell 'cell1' (0x529be0) for row 24...
2009-05-24 20:13:54.860 Table1-2[8919:20b] reusing cell 'cell1' (0x529a60) for row 25...
2009-05-24 20:13:54.893 Table1-2[8919:20b] reusing cell 'cell1' (0x529940) for row 26...
2009-05-24 20:13:54.943 Table1-2[8919:20b] reusing cell 'cell1' (0x520970) for row 27...
2009-05-24 20:13:54.993 Table1-2[8919:20b] reusing cell 'cell1' (0x5297d0) for row 28...
2009-05-24 20:13:55.060 Table1-2[8919:20b] reusing cell 'cell1' (0x528940) for row 29...
2009-05-24 20:13:55.126 Table1-2[8919:20b] reusing cell 'cell1' (0x5292f0) for row 30...
2009-05-24 20:13:55.193 Table1-2[8919:20b] reusing cell 'cell1' (0x526560) for row 31...
2009-05-24 20:13:55.293 Table1-2[8919:20b] reusing cell 'cell1' (0x5287e0) for row 32...
2009-05-24 20:13:55.393 Table1-2[8919:20b] reusing cell 'cell1' (0x5293c0) for row 33...
2009-05-24 20:13:55.543 Table1-2[8919:20b] reusing cell 'cell1' (0x5284c0) for row 34...
2009-05-24 20:13:55.726 Table1-2[8919:20b] reusing cell 'cell1' (0x52e290) for row 35...
2009-05-24 20:13:56.043 Table1-2[8919:20b] reusing cell 'cell1' (0x529be0) for row 36...
2009-05-24 20:13:57.194 Table1-2[8919:20b] reusing cell 'cell1' (0x529a60) for row 37...

If you study the output closely - especially the pointers within the parentheses - you'll see that the cells are reused in a recurring pattern. This suggests that all the 'cell1' UITableViewCell objects are placed on a circular queue inside UITableView. Once a cell has been removed from the head of  the queue and reused it is placed at the tail of the queue again. That a queue is used in the "cache" implementation is further suggested by the method name 'dequeueReusableCellWithIdentifier' since "dequeue" is a fancier way of saying "remove from the head".

Resetting cell contents before reuse

The solution to all these "strange" problems is to reset the contents of a reused cell before returning it to the table view. In our case that means setting the 'text' property every time - both for newly created cells and for reused cells.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

// try to retrieve "cell1" from the UITableView cache
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell1"];
if ( cell == nil ) {
// "cell 1" wasn't present in the cache, so create it
cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"cell1"];
NSLog(@"creating cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);
} else {
// "cell 1" was present in the cache, so log that we're reusing it
NSLog(@"reusing cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);
}

// reset cell contents
cell.text = [NSString stringWithFormat:@"this is row %d", indexPath.row];
return cell;
}

Summary

Ok, now we have digged further down into the world of the UITableView class and hopefully it's even less frightening now that we have examined how the cell reuse scheme works. We're still very sloppy with our memory management - in fact, we're completely ignoring it - so bear in mind that these small tests are just that - small tests to increase our understanding.

Saturday, May 23, 2009

iPhone tutorial: UITableView from the ground up, part 1

In a previous post (which I recommend that you read), I wrote quite a lot about UITableView, but I also mentioned that I would probably return to the subject in the future. Now that time has come! The reason for this is that I'm still a bit scared of the class and always try to avoid it when designing user interfaces. Sure, there are merits in fitting all information on a single screen since it forces you to really think about how to interact with your application and how to minimise the number of settings. However, understanding a powerful class like UITableView also has its merits since it can save you a lot of time during both the design and implementation phases. Furthermore, it gives the user a consistent experience over all iPhone applications instead of having to learn each application's interface.

Avoiding the UITableView isn't that difficult as long as you're working with small pieces of information, but what do you do if you have a list of a hundred things you want to display? Choose a really small font? Use a tab controller with five tabs showing 20 things per tab? Use a navigation controller with "Next"-buttons? Use a UIScrollView? As you can see, there is no shortage of alternative solutions and some can probably work very well, but you should probably have a very strong reason for inventing something new opposed to using the UITableView since this is designed to show long lists of information.

As you can see in the title of this post, this is part 1 of a series of UITableView tutorials. This is because the subject is huge and I think it will be easier to understand piece by piece instead of all at once even if it means that some overview is lost. To compensate for the lost overview I will try to post the parts in quite tight succession.

Let's create the XCode project for this tutorial! Start XCode and choose "File/New project" from the menu. Select the "Window-Based Application" template and name it "Table1".

Resources/MainWindow.xib

Double-click on MainWindow.xib to start Interface Builder (IB) and load the xib-file. As always when doing anything but the most trivial tasks in IB, I recommend that you switch to hierarchical view mode by pressing the middle icon above the "View mode" text in the upper left corner. Bring up the library window in IB (CMD-L) and drag the "Table View" icon from the "Data views" section and drop it onto the "Window" object in the MainWindow.xib window. A small arrow should appear to the left of the "Window" object to indicate that other objects are embedded into it. Press the arrow and the hierarchy under the object should appear; in this case just the "Table View" object. If you double-click the "Table view" object the "design window" for that object appears, in this case the design window shows a UITableView with a list of cities in California.

Now we should make the "Table1 App Delegate" object the delegate of the "Table View" object, so CTRL-drag from "Table View" to "Table1 App Delegate", first to connect it to the 'dataSource' outlet and then to the 'delegate' outlet.

Classes/Table1AppDelegate.h

Above, we connected the application delegate object to both the 'delegate' and the 'data source'for our table, so let's specify that this class is going to implement the UITableViewDelegate and UITableViewDataSource protocols. We do this to allow the compiler to help us check that we have implemented all the required methods. Do this by editing the h-file so that the interface-line looks like this:

@interface Table1AppDelegate : NSObject <UIApplicationDelegate, UITableViewDelegate, UITableViewDataSource> {

If you build the project (CMD-B), you'll see that the compiler issues the following warnings:

Table1AppDelegate.m:29: warning: incomplete implementation of class 'Table1AppDelegate'
Table1AppDelegate.m:29: warning: method definition for '-tableView:cellForRowAtIndexPath:' not found
Table1AppDelegate.m:29: warning: method definition for '-tableView:numberOfRowsInSection:' not found
Table1AppDelegate.m:29: warning: class 'Table1AppDelegate' does not fully implement the 'UITableViewDataSource' protocol

From this output we can immediately see that we need to implment the 'tableView:cellForRowAtIndexPath:' and 'tableView:numberOfRowsInSection' methods of the UITableViewDataSource protocol. I wrote quite a lot about these methods in my first UITableView post so I won't do a full "background" here, but instead focus on the implementation.

If you want to find out what happens if you ignore this warning and refrain from implementing them, go ahead and build and run (CMD-Return) the application and watch it crash in the simulator. After the crash, check out the XCode console window (CMD-R) to see what went wrong. You'll see something like this:

[Session started at 2009-05-23 09:58:36 +0200.]
2009-05-23 09:58:40.887 Table1-2[7110:20b] *** -[Table1AppDelegate tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x523870
2009-05-23 09:58:40.893 Table1-2[7110:20b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[Table1AppDelegate tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x523870'

It seems someone tried to call 'numberOfRowsInSection', which wasn't implemented ("unrecognized selector sent to instance...").

Classes/Table1AppDelegate.m

numberOfRowsInSection

We'll start with 'numberOfRowsInSection' since that's the most basic of the two. It lays the foundation for the table by specifying how many rows it should have, which in our first example will be three (3). Add the following to the end of the file.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 3;
}

When I first saw this method I really thought that there was some magic going on - I couldn't grasp what the stuff before the 'numberOfRowsInSection' part of the definition meant. My initial thoughts was that perhaps this method returns two things; a UITableView pointer as well as an NSInteger? Or is there some method overloading or other inheritance-realted Objective C-magic going on?

A short digression on Objective C method names

Once the chock of the strange syntax had worn off , I realised that this method just have a rather strange name. Or actually, you could say it has no name at all - all it has is a list of argument names! If we break it down, it looks like this:

- (NSInteger)
tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {

So there are three components; the first is the return value, the second is argument 1 and the third is argument 2. Each argument is composed of a "label" or short descrptive text ("tableView" and "numberOfRowsInSection"), a colon (':') and the argument name ("tableView" and "section"). Well, that is not the full truth. It could also be broken down like this:

- (NSInteger)
tableView
:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {

Here we have four components; return value, method name, argument 1 and argument 2. Here the "label" for argument 1 ("tableView") is instead used to specify the method name.

Confusing, huh? In fact, this is all pretty standard Objective C - the method name and first argument label/descriptive text are fused into one - but what's "wrong" with this method in my view is the choice of the method name and/or argument label names. However, there actually exists an acceptable explanation for this naming scheme. Lot's of methods in "delegation" protocols are named according to this scheme and the reason for that is to provide the "delegation context" as the first argument. This is because a single object could be the delegate for several tables for example. If so, the delegate object needs to find out which table is requesting information from it. That's why the first argument is a UITableView pointer.

A more intutive name for this method could have been achieved by rearranging the components a bit:

- (NSInteger)numberOfRowsInSection:(NSInteger)section tableView:(UITableView *)tableView {

cellForRowAtIndexPath

After the digression on Objective C method names above you probably understand why I left out the tableView "prefix" of the method name in the title above - it's so much easier to refer to the method by just saying 'cellForRowAtIndexPath' even though it's correct name is 'tableView:cellForRowAtIndexPath' since it is defined like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

This method is called by the table to create the objects used to display the contents of a cell (row) in the table, that is, objects of the class UITableViewCell. Let's do just that - create an UITableViewCell object and return it by adding the following to the end of the file.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:nil];
cell.text = @"my cell";
return cell;
}

Two things are worth explaining here, or rather worth mentioning since the explanation is available in the API docs for 'initWithFrame:reuseIdentifier' in the UITableViewCell class. What I'm talking about is 'CGRectZero' which is used to specify the size of the cell and the 'nil' value for the 'reuseIdentifier'. We pass 'CGRectZero' since the API docs tells us to and 'nil' since we don't want to reuse the cell. By the way, we'll talk about reusing cells in the next part of this tutorial.

Build and run (CMD-Return) in XCode and you should see a table containing three rows, all containing the text "my cell". Try touching the rows and they should turn blue when they are selected. If you try to select the fourth row, nothing will happens since our table only has three "active" rows. Maybe not that impressive, but also not that complicated to make, huh?

Summary

We have managed to display a table view with just a few lines of code and have hopefully managed to overcome some of our fear of table views. As always in my tutorials, I do "cheat" a little to keep things simple. For example, I do know that proper memory management is very important, but at this point it would just be in the way and introduce unwanted complexity. Furthermore, it's often quite easy to add once you understand the underlying classes you're working with.

Wednesday, May 13, 2009

iPhone tutorial: Storing and retrieving information using plists


Some applications need to store information in order to be really valuable for the user. Others do it just for fun; for example, storing the highscores in a game. Yet others do it to offer a multi-platform experience, by making it possible to access the same information from several different platforms (web, desktop, mobile), where the iPhone might be one of them. In the last case, the information should probably be stored on a server on the Internet to make it really useful.

There are several ways to store information persistently on the iPhone, but in this tutorial we're going to focus on "plists", or property lists as they really are called. If you already have done some iPhone development or if you've read the previous tutorials, you've already come in contact with property lists. That's because the Resources/Info.plist file present in all XCode template projects is a property list.

If you click on it in XCode, the contents of the file will be presented in a "plist-editor" view, which makes it easy both to browse the information in the plist as well as edit it. If you instead open the file in an editor which understans xml (for example Dashcode), you'll see that Info.plist is an xml file.

Ok, so a plist seems to be a standardised xml format for storing information on the iPhone. That's good, but what kind of format can be stored? Unless you have very specific needs, I would say almost anything. Plists basically contain a collection of key-value pairs, where the keys have to be unique and the values have to be objects.  That is, you cannot store primitive types like 'int' and 'long' without first wrapping them in an Objective C object. You cannot store any Objective C object, though, but you come a long way with the ones that are supported:

NSArray
NSDictionary
NSString
NSData
NSDate
NSNumber(intValue)
NSNumber(floatValue)
NSNumber(boolValue == YES or boolValue == NO)

We said that plists contain a collection of key-value pairs. Such collections are often implemented using data types such as hash tables, hash maps or dictionaries, which more or less are the "same thing", but can also be implemented using arrays. When an array is used, the array indices are the keys, while, in the dictionary case, the keys often are strings. In the list of supported objects above, you can see that NSDictionary and NSArray present. This is extra interesting since a plist itself often is implemented using a dictionary or array object, called the root object.

If you want to read more about property lists at this stage I recommend that you open the API docs browser in XCode and do a full text search for "property list". Then you should find a document called "Property List Programming Guide".

A simple plist application

Now let's get down to programming! Start a new "Window-Based" project in XCode and name it "plist1", by choosing "File/New project" from XCode's menu. We're not going to do anything "graphical" in the tutorial, but we'll use a window-based project anyway because it's an easy way to create a new project.

Resources/foo.plist

How to create a new plist-file in XCode isn't totally obvious, even though it is very easy once you find out how. CTRL-click on the "Resources"-folder/group in XCode and choose "Add/New file" from the pop-up menu. In the template window that appears, choose the "Other" section under the "Mac OS X" heading. There you will be able to select "Property list" - do that and name the file "foo". If you click on Resources/foo.list the plist-editor will appear and you'll see that the plist is empty, apart from the root object which we mentioned above. You'll also see that it is of type 'Dictionary'.

If you click on the small icon at the end of the "Root"-line (to the right) a new line will appear under the root object, indented one level to indicate that it is contained inside the root object. The name of the key is set to "New item", but change this to "key1". We're happy with the default type - String - since we're going to use a string for our first test. Set the value to the text "value1" though, by double-clicking in the value-column.

If you build the project (CMD-B) - an easy way to save all files! - and locate "foo.plist" on your hard disk using 'Finder' in Mac OS X, you'll see that it opens in 'Property List Editor' when you double-click it. That editor looks just like the one that's used in XCode if you click on the file there. If you instead open "foo.plist" with an xml-capable editor - like 'Dashcode' in Mac OS X -  you'll instead see that it contains xml (see the picture).

As you can see, the file actually contains xml. First comes some standard xml boilerplate stuff and after that, there is an opening "tag" called 'plist'. Inside that is the root-node which is of the type 'dict' (NSDictionary). Inside the root node is a 'key'-tag containing 'key1' and a 'string'-tag  containing 'value1'. What all this means is that this file contains a plist, which contains a dictionary, which contains the key-value pair "key1=value1" where the value is of the type 'string' (NSString). Pretty self-explanatory, huh?

Classes/Plist1AppDelegate.m

We're going to "hijack" the 'applicationDidFinishLaunching' method for our short test, so edit the method to make it look like below. The inlined comments tries to explain what's going on, so we won't analyse the code any further.

- (void)applicationDidFinishLaunching:(UIApplication *)application {    

    // Override point for customization after application launch
    [window makeKeyAndVisible];

// create a pointer to a dictionary
NSDictionary *dictionary;
// read "foo.plist" from application bundle
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:@"foo.plist"];
dictionary = [NSDictionary dictionaryWithContentsOfFile:finalPath];

// dump the contents of the dictionary to the console
for (id key in dictionary) {
NSLog(@"bundle: key=%@, value=%@", key, [dictionary objectForKey:key]);
}
}

Test run

Build and run in XCode (CMD-Return) and wait until the iPhone simulator appears. Once it does, click on the source code window in XCode and press CMD-R to bring the console window to the front. In it you should see something like this:

2009-05-14 07:30:07.113 Plist1[659:20b] bundle: key=key1, value=value1

Pretty cool! With just a few lines of code, we were able to read an xml file containing a dictionary and dump the contents of the dictionary to the console. Reading a file from the application bundle which we did here, is an ideal way of handling "static data" like a default configuration or why not the initial high score table of a game? Whenever the user wants to "reset to defaults", you just have to read the data from the bundle again.

Saving a plist to a file

Being able to save the contents of a plist to a file is of course very valuable and very useful. It allows you store "dynamic data" produced by your application to make it available to the user the next time the application is started. If it is the highscore table of a game you're saving, it's essential if the player wants to show the highscores to a friend. If not, the player would have to keep the application running until he or she meets the friend. That's far from practical, especially considering the rather short battery life of the iPhone ;)

Classes/Plist1AppDelegate.m

Replace the "create a pointer to a dictionary" code at the beginning of the 'applicationDidFinishLaunching' method we created above with the following:

// create a pointer to a mutable dictionary
NSMutableDictionary *dictionary;

We're changing the class of the dictionary from NSDictionary to NSMutableDictionary in order to be able to make changes to the dictionary. (Mutable is a fancy way of saying that something can change, or "mutate".)

Then add the following to the end of the 'applicationDidFinishLaunching':

// create a NSNumber object containing the
// integer value 2 and add it as 'key2' to the dictionary.
NSNumber *number = [NSNumber numberWithInt:2];
[dictionary setObject:number forKey:@"key2"];
// dump the contents of the dictionary to the console
for (id key in dictionary) {
NSLog(@"memory: key=%@, value=%@", key, [dictionary objectForKey:key]);
}

// write xml representation of dictionary to a file
[dictionary writeToFile:@"/Users/henrik/Sites/foo.plist" atomically:NO];

What we do here is add a key-value pair to the dictionary (this is possible since we now have a NSMutableDictionary). Both the key and the value has to be objects, so we create a NSNumber object containing the integer 2 and associate it with a NSString object containing the text "key2". To verify that the new key-value pair was added, we once again dump the contents of the dictionary to the console. After that comes the exciting stuff. With a single method call 'writeToFile' on our 'dictionary' object, the contents of the plist is written in xml format to a file of our choice. In this case we chose to save it in a directory called 'Sites' in my home directory. You should replace 'henrik' with your Mac OS X username to make it work on your computer.

Test run

Build and run in XCode (CMD-Return), wait for the iPhone simulator to start and then go back to XCode and press CMD-R to bring the console to the front. There you should see the following:

2009-05-14 17:29:09.064 Plist1[863:20b] bundle: key=key1, value=value1
2009-05-14 17:29:09.075 Plist1[863:20b] memory: key=key1, value=value1
2009-05-14 17:29:09.075 Plist1[863:20b] memory: key=key2, value=2

The first line contains the same information as in the previous test run. The second and third lines dump the contents of our changed (mutated) dictionary and thus we see that the key-value pair we added also shows up (key2). The first line is prefixed with "bundle:" to indicate that it shows the contents of the plist read from the bundle, while the other lines are prefixed with "memory:" to indicate that they show the contents of the plist in the iPhones memory - the one we mutated...

Let's take a look the contents of the file that was written to the "Sites"-directory in our Mac OS X home directory. Locate "foo.plist" in "Finder" and double-click on it to load it into the "Property List Editor" and you'll see that it now also contains the key-value pair we added programmatically in the source code above. You can also open it in an xml capable editor like "Dashcode" to verify that the key-value pair is xml encoded in a similar way as the key-value pair we added from the plist editor in XCode.

Reading a plist from a web server

Being able to read information from a web server in a simple way is a really powerful feature. Thankfully, this feature is available on the iPhone and extremely simple to use if the information you want to read can be contained in a plist.

Configuring the web server in Mac OS X

In order to test this, we need access to a web server. This is not a problem since Mac OS X comes pre-packaged with the Apache web server. All you have to do is enable it. You do this by going to the "System settings" in Mac OS X (the "gears" icon in the dock), select "Sharing" and enable "Web sharing". Please excuse me for a bit sketchy here, but I'm using a Swedish version of Mac OS X and therefore don't know the exact names of these settings in the English version. It's pretty simple though.

When you're on the "web sharing" page in the system settings, you might notice that there also is an edit box where you can see/edit the network name of your computer. Under this edit box there also is a small text which says (freely translated): "Other computers on the local network can access your computer as Name.local", where "Name" is the name of your computer. My computer is called "MacBook", so I'll refer to that from now on.

To test that your web server works, open up a web browser and type the following:

  http://MacBook.local/~henrik

Once again, remember to replace "MacBook" with the name of your computer and replace "henrik" with the username you use in Mac OS X. If it works you should see a small text titled "Your web site" (freely translated). Now test the following URL instead:

  http://MacBook.local/~henrik/foo.plist

Hey, that's the xml representation of our plist! The one we saved to the "/Users/henrik/Sites/foo.plist" path on our hard disk above. How did it end up in our web browser? It turns out that the "Sites"-directory in your home directory contains the files for your local web site.

Now that we have set up and tested our local web site it becomes really easy to experiment with "web enabled" iPhone applications. Just place the files you would read from the web in your Sites-directory.

Classes/Plist1AppDelegate.m

Reading a plist from a web server URL is really simple. Just add the following to the end of 'applicationDidFinishLaunching'

dictionary = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:@"http://localhost/~henrik/foo.xml"]];
for (id key in dictionary) {
NSLog(@"web key=%@, value=%@", key, [dictionary objectForKey:key]);
}

Test run

Build and run in XCode (CMD-Return) and you should see something like this in the console window:

2009-05-15 07:40:40.691 Settings1[426:20b] bundle: key=key1, value=value1
2009-05-15 07:40:40.692 Settings1[426:20b] memory: key=key1, value=value1
2009-05-15 07:40:40.694 Settings1[426:20b] memory: key=key2, value=2
2009-05-15 07:40:40.719 Settings1[426:20b] url: key=key1, value=value1
2009-05-15 07:40:40.719 Settings1[426:20b] url: key=key2, value=2

It's the last two lines - the ones prefixed with "url:" that are new compared to the last test run. Those lines come from the dump of the dictionary which we read from our web server url, and as you can see they are identicaly to the "memory:"-lines. That's because we wrote the memory-dictionary to a file in the "Sites"-directory of our home directory in Mac OS X and then read it back via the web server in Mac OS X.

Summary

Being able to access information stored in powerful data structures (hash tables, arrays) as easy is this is indeed a blessing, since creating proprietary file formats and then writing parsers for them, etc. can be quite time consuming. That it's equally easy to access information stored on a web server is fantastic since you can "Internet enable" your application with just a few lines of code. The only restriction with the methods presented in this tutorial is that the information as to be stored in the plist xml format , so you can't access any type of information this easily - just the information you have control over yourself.