iOS Apps with Tasty UI

For the next few chapters of this series, we’re going to construct an application to help amateur ornithologists identify and record birds they encounter. We’re going to start very simply and build up the app bit-by-bit. This is the Orny series.

Setting Up

To get started, open Xcode and click ‘Create a new Xcode project’ and, as in the last tutorial, select ‘Window-based Application’. This time we’re going to use Xcode’s built-in Interface Builder to create the user interface.

Orny 1 Figure 1

Figure 1

On the next screen, give it a name. I’m calling mine ‘Orny’, so you should probably follow along with that. Make sure ‘Device Family’ is set to ‘Universal’, and check the ‘Use Core Data’ and ‘Include Unit Tests’ boxes. We’re not going to do anything with CoreData and Unit Tests this time around, but we will in a future tutorial: the plan is that we’ll keep modifying this app as the series goes on, until we have something that could hypothetically be released!

Making an app ‘Universal’ means that it will work on both iPhones and iPads. It’s a lot more work to create a UI that suits both device families, because it usually means creating two separate UIs backing onto the same Controllers.

Orny 1 Figure 2

Figure 2

Hit ‘Next’ and Xcode will generate your application.

Your Many-Splendoured AppDelegates

Alright. It’s just like before, but different! You’ll notice you now have not one, but three AppDelegates, namely OrnyAppDelegate, OrnyAppDelegate_iPhone, and OrnyAppDelegate_iPad. Whoah! What’s going on!?

Orny 1 Figure 3

Figure 3

Remember we’ve made this app Universal. Xcode doesn’t know ahead of time which device this application is going to run on, so it gives each device a separate entry point into your code. iPhones run OrnyAppDelegate_iPhone, and iPads run OrnyAppDelegate_iPad. This means you can implement entirely different ViewControllers and Views for each device.

“But wait!” you object helpfully, “surely that means I have to do double the work?”

Not so, gentle developer! Your two device-specific AppDelegates inherit functionality from your OrnyAppDelegate class. This means that any code you write on OrnyAppDelegate is also present on OrnyAppDelegate_iPhone and OrnyAppDelegate_iPad. For free.

Of course, you might want to override a method on your base OrnyAppDelegate class, just for the iPad version. You can do that too because any messages implemented in OrnyAppDelegate_iPad or OrnyAppDelegate_iPhone will override messages with the same signature (name and arguments) on the parent.

You can see where we’re declaring one class to be the parent of another on line 12 of OrnyAppDelegate_iPhone.h:

@interface OrnyAppDelegate_iPhone : OrnyAppDelegate {

}

That @interface OrnyAppDelegate_iPhone : OrnyAppDelegate roughly translates to let there exist a class called OrnyAppDelegate_iPhone that is a child of (i.e. inherits functionality from) OrnyAppDelegate.

XIB Files

You’ve also got some XIB files, MainWindow_iPhone.xib and MainWindow_iPad.xib. XIB files are Xcode’s representation of a View, they are, in effect, the User Interface of your application.

If you click on one, you’ll open the Interface Editor. We’re going to explore this view momentarily.

Orny 1 Figure 4

Figure 4

One ViewController to Serve Them All

The first thing we want our App to be able to do is display a list of local bird species. We’re going to create a data structure to contain the information we want to display, then use a ‘UITableView’ to display them to the user.

Create the BirdListViewController

Let’s add a ViewController. We’re going to share this one across our two AppDelegates and our two device families, because we can do that.

Click ‘File > New File’ or hit ‘Command+N’.

Orny 1 Figure 5

Figure 5

Select ‘UIViewController’.

Orny 1 Figure 6

Figure 6

Leave the subclass-of field set to ‘UIViewController’, but make sure ‘With XIB for User Interface’ is checked. We want that!

Enter ‘BirdListViewController’ into the Save As field, and hit ‘Save’. Ensure you are saving into the ‘Orny’ project and into the folder or Group also called ‘Orny’ with the default target of the ‘Orny’ application.

You should now have 3 additional files:

  • a header file, BirdListViewController.h
  • a message file, BirdListViewController.m
  • a XIB file, BirdListViewController.xib

Nice!

Preparing our Data

We need a structure to hold the data we want to show to the user. We’re going to create a nested data structure in our BirdListViewController, an NSArray containing an NSDictionary!

Make your BirdListViewContoller.h look like the following:

@interface BirdListViewController : UIViewController {

}

- (void)loadBirdData;

@property (nonatomic, retain) NSMutableArray *birds;

@end

We’ve added a method signature - (void)loadBirdData and a @property.

Remember – we need to @synthesize our @properties, so add to BirdListViewController.m, after the @implementation declaration, the following code line: @synthesize birds;

Further down the file (somewhere towards the end), add the following message implementation to BirdListViewController.m:

#pragma mark UITableViewDataSource methods

- (void)loadBirdData {
    birds = [[NSMutableArray alloc] init];

    // Add a Magpie to our bird array
    [birds addObject:
        [NSDictionary 
         dictionaryWithObjects:[NSArray arrayWithObjects:@"Magpie", @"magpie.jpg", @"Black and white and crafty all over!", nil]
         forKeys:[NSArray arrayWithObjects:@"name", @"image", @"description", nil]
        ]
     ];

    // And another!
    [birds addObject:
     [NSDictionary dictionaryWithObjects:
      [NSArray arrayWithObjects:@"Rosella", @"rosella.jpg", @"A red and blue parrot", nil] 
      forKeys:[NSArray arrayWithObjects:@"name", @"image", @"description", nil]
      ]
     ];     
}

#pragma mark defines a ‘note’ to ourselves about the code that follows; it’s like a comment, but it can be used by Xcode to bookmark sections of our code.

We’ve allocated memory for, and instantiated, our birds property as an NSMutableArray. An Array represents a collection of objects. An NSArray is immutable, that is, when it’s created, it contains whatever you throw into it with an instantiation method like initWithObjects. After that, you can’t add or remove items.

By contrast, an NSMutableArray is dynamic, and can have a great number of objects added to it dynamically, within device memory constraints.

We go on to add two Objects to our NSMutableArray birds both of them NSDictionaries. A Dictionary is a collection of key-value pairs, like an associative array in PHP. NSDictionary, like NSArray, is immutable but it has a subclass, NSMutableDictionary, that can be modified after creation.

You can see that we’re adding another Array to the Dictionary, containing the values we want the Dictionary to reference. We then pass another Array, containing the keys we want to reference those values. The position of a key in its Array (name, image, description in the list) corresponds to the position of the value it represents (@”Mapie”, @”magpie.jpg”, @”Black and white and crafty all over!”)

The values we’re adding are all strings. In Objective-C, we denote a string using an @-symbol, @"like this".

As a final note on this function, we end any list of objects with ‘nil’, to let the object we’re passing the list to know “Hey, this list is all done now.”

On Memory and Leaks

I don’t want to get too far into memory management issues here, but this one needs to be said. We’ve alloc‘d an object, which means we’ve reserved memory for it. We have to give that memory back eventually, or else we end up holding onto it forever, which is a memory leak.

Andrew Markham’s article on iPhone Memory Management gives a good rundown on memory management issues, but to keep it short and sweet, we need to release that which we allocate. So, make BirdListViewController.m‘s dealloc method look like this:

- (void)dealloc
{
    [birds release];
    [super dealloc];
}

Almost done here!

The Awakening

We’ve defined a message to create our data structure and store it, but so far, we haven’t called it. The code will never execute!

Our ViewController is going to be pulled into existence, later on, by another object, in this case, the Window of our app. We need some way to call our method when this happens, and that method is known as awakeFromNib.

Add the following code under your initWithNibName method:

- (void)awakeFromNib {
    [self loadBirdData];
}

Edit the View

Now we’ll want to edit our XIB file and add a UITableView. Click on BirdListViewController.xib.

Orny 1 Figure 7

Figure 7

If you don’t see the Utilities panels on the right, as in Figure 7, bring them into view using the right-most of the three ‘View’ buttons located at the top right hand side of the Xcode interface.

First up, select the File Owner object from the list of Objects to the left of the editor, which looks like a transparent 3D box. Bring up the Outlets menu, using a right-click on the Object, and draw a line from the circle to the right of ‘View’ to your UIView, which is the big expanse of white with a border around it.

These icons on the left-hand side of the Interface Editor represent “connections” to code objects, typically. They’re not the actual code objects themselves, they’re pointers to some arbitrary chunk of code that you can define and redefine. When the app is run, iOS knows what to do with them.

Next, From the Object browser located at the bottom of the Utilities panel, select a ‘UITableView’ and drag it onto the white ‘View’ located in the middle of the Interface Editor.

Click the table to select it, and then click the right-pointing arrow at the top of the right-most toolbar, which is the Connections Inspector of the Utilities panel. There you will see the Outlets for the ‘UITableView’ we just added. Note, you could also right-click the ‘UITableView’ to get a similar menu to pop up.

Orny 1 Figure 8

Figure 8

Mouse over the circle to the right of ‘dataSource’ within Outlets. Click-and-drag to draw a line all the way over to ‘File’s Owner’, which you’ll recall is the transparent 3D box, as in Figure 9. You’ve now told your ‘UITableView’ to pull data from your ‘BirdListViewController’. Whew!

Orny 1 Figure 9

Figure 9

Etiquette and Protocol

UITableView needs any object connected to it as a ‘dataSource’ to conform to a Protocol. This is a contract between objects, saying that they support, and expect, certain methods to be implemented.

We need to specify that our ‘BirdListViewController’ intends to conform to the ‘UITableViewDataSource’ protocol, so change its definition in BirdListViewController.h to look like this:

@interface BirdListViewController : UIViewController <UITableViewDataSource> {

}
...

Intentions only get us so far, we also have to implement some methods to back up our claim of protocol support. Add the following code to BirdListViewController.m at the bottom where we left off before:

- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {
    return [birds count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *newCell = [[[UITableViewCell alloc] init] autorelease];
    NSDictionary *thisBird = [birds objectAtIndex:[indexPath row]];

    UILabel *newCellLabel = [newCell textLabel];
    [newCellLabel setText:[thisBird objectForKey:@"name"]];

    return newCell;
}

// fixed font style. use custom view (UILabel) if you want something different
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return @"Some Birds";
}

A Protocol defines both required and optional methods. Here, we’re only implementing the required methods, and our cellForRowAtIndexPath could use some improvements, but we should cover these items in more detail in a later article.

Almost! Almost!

We’re almost there now.

The last thing we need to do is set the ‘rootViewController’ of our iPad and iPhone ‘UIWindows’. Repeat the following steps for both MainWindow_iPhone.xib and MainWindow_iPad.xib:

  1. Open the relevant XIB file.
  2. Select a UIViewController from the Objects library.
  3. Drag-and-drop it to the list of entities, the icons at the left of the editor.
  4. Select the UIViewController, and from the Utilities panel, click the third icon from the left, which is the Custom Class and Identity Pane.
  5. Change the name of the Class to ‘BirdListViewController’.
  6. Click on the Window, the very plain white square in the icons to the left of the Interface Editor, right click again to show it’s Outlets.
  7. Drag-and-draw a line from the circle at the right of the ‘rootViewController’ outlet to the ‘BirdListViewController’ object in the list on the left.

Steps 1 to 6 are demonstrated in Figure 12, you can see the ‘UIViewController’ highlighted at the bottom right in the Object library, the new class name at the top in the Identity Inspector, and the resulting ‘Bird List View Controller’ pointed out.

Orny 1 Figure 10

Figure 10

Also note the plain white ‘Window’ icon, which is connected to the ‘Bird List View Controller’ in steps 6 and 7. And you remember this needs to be done for both the iPhone and iPad XIB files, right? Phew!

Execute! Execute!

Hit Run. You should see a ‘UITableView’ populated with our two rows containing Magpie and Rosella. You can go from the Simulator back to Xcode, select the other Simulator and hot Run again, so as to test both devices.

We’re done for this chapter. We have covered the creation of a Universal app, managing data in a single ViewController and editing the View, and finally, utilising that for both iPhone and iPad. Next time, we’ll look at adding some interactivity!

The “Orny” Series

Andy White is providing an intense meditation of developing apps on the iOS platform at BuildMobile. With the prerequisite of having a tasty refreshing beverage in hand, use the tag to all Orny articles, or jump straight into an article specifically from this index.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://sitepoint.com Kristen Holden

    Great post Andy. Awesome to see an old face around as well :)

  • http://www.wistful-thinking.com Thom Parkin

    Great series! Thanks for this terrific tour of iOS development.

    {Two typos on this entry: “…select the other Simulator and hot Run again” and “Steps 1 to 6 are demonstrated in Figure 12.” There is no Figure 12