Chapter 4. multiple views: A table with a view

I like my coffee with two sugars, cream, a sprinkle of cinnamon, stirred twice, then ...

Most iPhone apps have more than one view.

We've written a cool app with one view, but anyone who's used an iPhone knows that most apps aren't like that. Some of the more impressive iPhone apps out there do a great job of moving through complex information by using multiple views. We're going to start with navigation controllers and table views, like the kind you see in your Mail and Contact apps. Only we're going to do it with a twist...

Look, I don't have time for posting to Twitter. I need to know a ton of drink recipes every night. Is there an app for that?

Sam, bartender at the HF Lounge

So, how do these views fit together?

Before you pick the template for our bartending app, take a minute to look at how you want the user to interact with the drink information. We're going to have a scrollable list of drink names, and when the user taps on a row, we'll show the detailed drink information using view #2, our detailed view. Once our user has seen enough, they're going to want to go back to the drink list.

Title

Drink #1

Drink #1

Drink #1

Name:

Ingredients:

Directions:

Once our users are done with the detailed information, the Navigation bar gives them a way to get back to the list.

Name:

Ingredients:

Directions:

We're going to want some kind of transition between these views...

We need a list of items to work with...

Title

Name:

Ingredients:

Directions:

We're going to be coming in and out of this view a lot – each time our user selects a drink.

The navigation template pulls multiple views together

For this app, we're going to use a Navigation-based project. To get started, go into Xcode and choose the File→New Project option. Choose the Navigationbased application and save it as DrinkMixer.proj. Make sure that "Use Core Data for storage" is not checked.

The navigation template comes with a lot of functionality built in:

Just like the name says, a navigation controller is built in. It provides back buttons, title bars, and a view history that will keep your user moving through the data without getting lost.

Don't check Core Data. We'll use that later in the book.

Lemon Drop

Firecracker

We have hierarchical data to organize. The navigation template helps us to move through the data, starting with a table view.

Lemon Drop: Citron vodka, lemon, and sugar. Add sugar to the rim of glass, pour ingredients into shaker...

Firecracker: Wild turkey and hot sauce. Pour ingredients into a rocks glass filled with ice.

The Navigation Controller provides transitions between views with animations.

The navigation template starts with a table view

The navigation template comes with a navigation controller and a root view that the controller displays on startup. That root view is set up as a table view by default, and that works great for our app. A table view is typically used for listing items, one of which then can be selected for more details about that item.

Navigation template

The navigation controller provides a navigation bar.

This is where you'll find the back buttons, forward buttons, and the title of the view you're in.

The table view

The table view provides an easy way to work with data. It starts with an empty, scrollable list for the main view of your application.

there are no: Dumb Questions

Q:

If the navigation template is about handing lots of views, why does it only come with one?

A:

Most navigation-based applications start out with a table view and show detailed views from there. How many detailed views, what they look like, etc. are very application-specific, so you have to decide what views you want and add those views. The navigation template doesn't assume anything beyond the initial table view.

Q:

What built in apps on iPhone use the Navigation control?

A:

Contacts and Mail, which are both core iPhone apps, use this design. It's a good idea to get into those apps on your phone to see how the entire template is implemented. For a neat twist, take a look at the Messages (SMS) app. That one uses a navigation controller but frequently starts in the "detail" view, showing the last person you sent or received a message from.

Q:

Do I have to use a table view for my root view?

A:

No, it's just the most common, since it provides a natural way to show an overview of a lot of data and have the user drill down for more information. Table views are very customizable, too, so some apps that might not seem like table views really are, like Notes or the iTunes store, for example.

A table is a collection of cells

The UITableView provides a lot of the functionality we need right away, but it still needs to know what data we're actually trying to show and what to do when the user interacts with that data. This is where the datasource and delegate come in. A table view is easy to customize and is set up by the template to talk to the datasource and delegate to see what it needs to show, how many rows, what table cells to use, etc.

The navigation controller, not the table view, provides the navigation bar. Since we're in interface builder, this is just a simulated one.

Table views have built-in support for editing their contents, including moving rows around, deleting rows, and adding new ones.

Table views can tell you when your user taps on a cell. It'll tell you the section and row that was tapped.

We're using the default table view cell, but you can create your own and lay them out any way you want.

Table views try to conserve memory by reusing cells when they scroll off the screen.

A table can only have one column, but you can put whatever you want in that column by customizing your table cells.

A table can have multiple sections, and each section can have a header and a footer. We only have one section, so we don't need either for DrinkMixer.

A table view is made up of multiple table cells. The table view will ask how many cells (or rows) are in each section.

there are no: Dumb Questions

Q:

How do cells get into that reusable list to begin with?

A:

The table view handles that. When cells scroll off the screen (either the top or the bottom,) the table view will queue up cells that are no longer needed. When it asks the datasource for a cell for a particular row, you can check that queue of cells to see if there are any available for use.

Q:

I don't understand the cell identifier... does it have to be "Cell"?

A:

No—that's just the default. When you do more complex table views, you can create custom cell types depending on what data you're trying to display. You use the cell identifier to make sure that when you ask for a reusable cell, the table view gives you back the type you expect. The identifier can be anything you want—just make sure you have a unique name for each unique cell type you use.

Drink List: Firecracker Lemon Drop Mojito

Wait, memory on the iPhone is a big deal, right? How can we put in all those drinks?

Like everything else on iPhone, the UITableView has to worry about memory.

So, how does it balance concerns about memory with an unknown amount of data to display? It breaks things up into cells.

Each drink gets its own cell... sorta

The UITableView only has to display enough data to fill an iPhone screen—it doesn't really matter how much data you might have in total. The UITableView does this by reusing cells that scrolled off the screen.

The cells that are off the view go into a bucket until iPhone needs memory or the table view can reuse them when the user scrolls.

When the table view has to scroll a new row onto the screen, it asks the datasource for a cell for that row.

ecracker

Captain

As the user scrolls, some cells slide off the screen.

This is the active view with the table cells that are currently visible.

Firecracker

Lemon Drop

Absolut Mixer

Bee Stinger

Cupid's

Mojito

Miami Vice

Captain

The datasource checks the cell bucket to see if there are any cells available to reuse. If so, it just replaces the row's contents and returns the row.

Datasource

If there aren't any for reuse, the datasource creates a new one and sets its content.

The tableview takes the new cell and scrolls it in...

there are no: Dumb Question

Q:

You mentioned the table view's datasource and delegate, but why didn't I have to declare anything like we did with UIPickerView?

A:

Great catch. Normally you would, but the navigation-based template we used already set this up. To see what's happening, look at the RootViewController.h file. You'll see that it is a subclass of UITableViewController, and that class conforms to the UITableViewDataSourceProtocol and the UITableViewDelegateProtocol. If you look in RootViewController.xib, you'll see that the table view's datasource and delegate are both set to be our RootViewController. If we weren't using a template, you'd have to set these up yourself (we'll revisit this in Chapter 7).

Q:

I noticed we used an NSMutableArray. Is that because we had to initialize it?

A:

No—both NSMutableArray and NSArray can be initialized with values when you create them. We're using an NSMutableArray because we're going to manipulate the contents of this array later. We'll get there in a minute.

Q:

What's the nil at the end of the rink names when we create the drink array?

A:

NSMutableArray's initializer takes a variable number of arguments. It uses nil to know it's reached the end of the arguments. The last element in the array will be the value before the nil—nil won't be added to the array.

Q:

Tell me again about that @ symbol before our drink names?

A:

The @ symbol is shorthand for creating an NSString. NSArrays store arrays of objects, so we need to convert our text names (char*s) to NSStrings. We do that by putting an @ in front of the text constant.

Q:

When we customized the table view cells, we used the cell.textLabel. Are there other labels? What's the difference between cell.textLabel and cell.text?

A:

Before iPhone 3.0, there was just one label and set of disclosure indicators in the default cell, and it was all handled by the cell itself. You just sent the text you wanted on the cell.text property. Nearly everyone wanted a little more information on the table cells, so in iPhone 3.0, Apple added a few different styles with different label layouts. Once they did that, they introduced specific properties for the different text areas, like textLabel, detailLabel, etc., and deprecated the old cell.text property. You shouldn't use cell.text in your apps—Apple will likely remove it at some point in the future. We'll talk more about the other labels later in the chapter.

Q:

You mention that we can use section headers and footers—how do you specify those?

A:

The datasource is responsible for that information, too. There are optional methods you can provide that return the title for section headers and the title for section footers based on the section number. They work a lot like our cellForRowAtIndexPath, except they only return strings.

Q:

What's the difference between a plain table view and a grouped table view?

A:

The only difference is the appearance. In a plain table view, like the one we're using, all the sections touch each other and are separated by the section header and footer if you have them. In a grouped table view, the table view puts space between the sections and shows the section header in bigger letters. Take a look at your contact list, then select a contact. The first view, where all of your contacts are listed together and separated by letters is a plain table view. The detailed view, where the phone numbers are separated from email addresses, etc, is a grouped table view.

Just a few more drinks

The drink menu at Head First Lounge has 40 cocktails.

Firecracker

Lemon Drop

Mojito

Absolut Mixer

Bee Stinger

Cupid's Cocktail

Strawberry Daquiri

Long Island Ice Tea

Captain and Coke

Miami Vice

Boxcar

Cat's Meow

Apple Martini

Manhattan

After Dinner Mint

Red Rudolph

Day at the Beach

Melon Tree

Rum Runner

Blue Dog

Key West Lemonade

Neapolitan

Polo Cocktail

Purple Yummy

Neon Geek

Flaming Nerd

Letter Bomb

Bookmaker's Luck

Baked Apple

Deer Hunter

Mexican Bomb

Aftershock

Black Eyed Susan

Beetle Juice

Terminator

Gingerbread Man

Lost in Space

Music City Sunset

Cafe Joy

Sandbar Sleeper

Get ready to start typing...

This sucks. Can't we just import the list Sam sent us somehow?

We could, but not the way we're set up now.

Since the drinks are populated with an array that's hardcoded into the implementation file, we can't import anything.

What would work well is a standardized way to read and import data; then we would be able to quickly get that drink list loaded.

Plists are an easy way to save and load data

Plist stands for "property list" and it has been around for quite a while with OS X. In fact, there are a number of plists already in use in your application. We've already worked with the most important plist, DrinkMixer-Info.plist. This is created by Xcode when you first create your project, and besides the app icons, it stores things like the main nib file to load when the application starts, the application version, and more. Xcode can create and edit these plists like any other file. Click on DrinkMixer-Info.plist to take a look at what's inside

Some of these items are obvious, like the icon file and the main nib to load.

Others are less obvious, but we'll talk more about them in later chapters.

Built-in types can save and load from plists automatically

All of the built-in types we've been using, like NSArray and NSString, can be loaded or saved from plists automatically. We can take advantage of this and move our drink list out of our source code.

- (void)viewDidLoad {
  [super viewDidLoad];

     NSMutableArray* tmpArray = [[NSMutableArray alloc]
 initWithObjects:@"Firecracker", @"Lemon Drop", @"Mojito",nil];
     self.drinks = tmpArray;
     [tmpArray release];

  // Uncomment the following line to display an Edit
button in the navigation bar for this view controller.
  // self.navigationItem.rightBarButtonItem = self.
editButtonItem;

}

We'll move our drink list out of the source code here and into a plist instead...

Arrays (and more) have built-in support for plists

Changing the array initialization code to use the plist is remarkably easy. Most Cocoa collection types like NSArray ad NSDictionary have built-in support for serializing to and from a plist. As long as you're using built-in types (like other collections, NSStrings, etc.,) you can just ask an array to initialize itself from a plist.

The only piece missing is telling the array which plist to use. To do that, we'll use the project's resource bundle, which acts as a handle to applicationspecific information and files.

-(void)viewDidLoad {
  [super viewDidLoad];
     NSString *path = [[NSBundle mainBundle] pathForResource:
@"DrinkArray" ofType:@"plist"];
     NSMutableArray *tmpArray = [[NSMutableArray alloc]
 initWithContentsOfFile:path];
     self.drinks = tmpArray;
     [tmpArray release];
                            ...

Ask the app bundle for a path to our DrinkArray plist.

Initialize the array using the contents of the plist.

RootViewController.m

Now we just need to get that detail view all set up, right?

Creating your detail view will complete the app.

The entire list of drinks is great, but Sam still needs to know what goes in them and how to make them. That information is going to go in the detail view that we sketched up earlier.

Use a detail view to drill down into data

Earlier, we classified DrinkMixer as a productivity app and we chose a navigation controller because we have hierarchical data. We have a great big list of drinks loaded, but what Sam needs now is the detailed information for each drink: what are the ingredients, how do you mix them, etc. Now we'll use that navigation controller to display a more detailed view of a drink from the list.

The standard pattern for table views is that you show more information about an item when a user taps on a table cell. We'll use that to let the user select a drink then show our detailed view. The detail view follows the same pattern as our other views:

When the user taps on a drink, we'll display the detail view.

Touch here.

View Controller

The table view's controller (our RootViewController) will get the touch information. It will tell the nav controller to show the detailed view.

The detail view shows all the elements that make up a drink - the ingredients and how to mix them.

Detail

View

Since the detail view only cares about the specific drink it's showing details for, the datasource will focus on one drink.

Datasource

View Controller

Just like our other views, the detail view will have a view controller. This one will be responsible for filling in the detail view.

A closer look at the detail view

We sketched out the detail view earlier—but we need to look more closely at what we're about to build.

The back button comes with the nav controller

It will be populated with "Name:" and the drink info, so we don't need a label

A couple of labels for the bottom two fields

Back button

UITextField for the drink name

UITextView for the ingredients

UITextView for the directions

Let's start building...

there are no: Dumb Questions

Q:

We keep drawing the datasource, view, and view controller as separate things, but then we stick them together into the same class. What's going on?

A:

It's all about the pattern. In general, you'll have a few defined in a nib, a view controller backing it, and a set of data it needs to work on. Whether these are combined into one class or not really depends on the complexity of your application. If you're not using Interface Builder, you can go completely off the deep end and have your single class create the view programmatically. We'll show more of that later in the book. Conceptually, however, you still have a view that's calling into the view controller when things happen. Likewise, you usually have one or more datasource protocols being realized somewhere that are providing data to your view.

Q:

Why do we have to move the *.xib file into the Resources group?

A:

You don't have to, but we recommend it to help keep your code organized. Different developers use different groups, things like "User Interface", "Business Objects", "Data Objects", etc. Xcode really doesn't care; it's just important that you know how your code is organized and you can find what you're looking for. Reusing a structure that others will recognize is a good practice so people can pick up your code quickly and you can understand their code. We use the templated defaults in this book.

Q:

What are other ways to save data?

A:

There are quite a few of them. We'll cover the more common ones in this book in different projects. The one you're using now, plists, is the simplest, but it does limit what you can save and load. That doesn't make it bad; if it works for what you need, it's a fine solution—it's just too limited for everything. There's a serialization method called NSCoding that works well for custom objects, but can make version migration a challenge. iPhone supports saving and loading to a database using SQLite. This used to be the preferred way to go if you have a lot of data or need to search and access it without loading it all into memory. However, with iPhone 3.0, Apple introduced Core Data. Core Data is a very powerful framework that provides an OO wrapper on persistence and has nearly all of the benefits of using SQLite. It's definitely not trivial to get started, but it's really powerful. We'll build an app on it later.

Q:

Why didn't you use a label for the name field?

A:

UITextFields allow you to have placeholder text that appears in the field when it's empty. Rather than using up screen space with a Name label, we chose to use the placeholder. If the meaning of the text shown on the screen is obvious to the user, consider using placeholder text.

Q:

So why didn't we use it for the ingredients and directions?

A:

We could have, but since those contain multiple lines of text, we wanted to break them up with labels clearly showing what they were. Ultimately it's an aesthetic and usability decision, not a technical one.

OK, so I have an order for a Melon Tree... but I still don't see the drink details.

Touch here

We still need to get that detail view to load when Sam selects a drink.

Use the navigation controller to switch between views

Now that we've got the table view populated and the detail view built, it's time to manage moving between the two views. The navigationbased template comes preloaded with the functionality we need:

  • A view stack for moving between views

    As users move back and forth, you can ask the navigation controller to display the appropriate view. The navigation controller keeps track of where the users are and gives them buttons to go back.

  • A navigation bar for buttons and a title

    The navigation controller interacts with the navigation bar to display buttons that interact with the view being shown, along with a title to help the users know where they are.

  • A navigation toolbar for view-specific buttons

    The navigation controller can display a toolbar at the bottom of the screen that shows custom buttons for its current view.

The UINavigationController supports a delegate, called the UINavigationControllerDelegate, that gets told when the controller is about to switch views, but for DrinkMixer we won't need this information. Since the views get told when they're shown and hidden, that's all we need for our app.

Now we need to get the table view and nav controller working together to display the detail view.

Navigation controllers maintain a stack of views

We've been dragging the navigation controller along since the beginning of this project, and now we finally get to put it to use. The navigation controller maintains a stack of views and displays the one on top. It will also automatically provide a back button, as well as the cool slide-in and out animations. We're going to talk more about the whole navigation controller stack in the next chapter, but for now, we're just going to push our new view onto the stack and let the controller take care of the rest. We just need to figure out how to get that new view.

When a row is tapped, tableview:did SelectRowAtIndexPath: indexPath is sent to the delegate.

Add Drink View

Drink Table View

Once the new view is created, we'll use the navigation controller to push the view onto the screen.

Delegate

When the delegate method is called, our RootViewController (the delegate) needs to create and push the detail view controller.

We'll use the tap notification in the table view delegate

When a table row is touched, the table view calls tableview:didSelectRowA tIndexPath: on its delegate. The table passes along an NSIndexPath (just like cellForRowAtIndexPath) that tells us which row was selected.

Here's where it gets interesting: our RootViewController is our delegate, so it needs to hand off control to the view controller for our detail view...

Instantiate a view controller like any other class

The only piece left to create is the view controller. Instantiating a view controller is no different than instantiating any other class, with the exception that you can pass in the nib file it should load its view from:

[[DrinkDetailViewController alloc] initWithNibName:@
"DrinkDetailView Controller" bundle:nil];

Once we've created the the detail view controller, we'll ask the NavigationController to push the new view controller onto the view stack. Let's put all of this together by creating the callback into the delegate and creating the new view controller to push onto the stack:

#import "RootViewController.h"
#import "DrinkDetailViewController.h"

Since we're going to create the new view controller, we need to include its header.

// Override to support row selection in the table view.
  - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
*)indexPath {

    // Navigation logic may go here -- for example, create and
 push another view controller.
  DrinkDetailViewController *drinkDetailViewController =
[[DrinkDetailViewController alloc] initWithNibName:@"
DrinkDetailViewController" bundle:nil];
  [self.navigationController pushViewController:
drinkDetailViewController animated:YES];
  [drinkDetailViewController release];
}

Here's the delegate callback - the indexPath tells us which row (drink) was selected.

Instantiate the controller...

...then push it onto the navigation stack.

Now that the navigation controller has the detail controller, we can release our reference to it.

RootViewController.m

Let's try this out...

So, now we can get to the detail view from the drink list, but there aren't any details in there. We don't have that info in our plist, do we?

We've outgrown our array

All that's left is to get the ingredients and directions in the detail view, and we'll have a bartender's brain. To save you from having to type in the ingredients and directions, we put together a new file with all of the extra information. The problem is we can't just jam that information into an array. To add the drink details to this version, we need a different data model.

Dictionaries store information as key-value pairs

Our current drink plist is just a single array of drink names. That worked great for populating the table view with just drink names, but doesn't help us at all with drink details. For this plist, instead of an array of strings, we created an array of dictionaries. Within each dictionary are three keys: name, ingredients, and directions. Each of these have string values with the corresponding information. Since NSDictionary adopts the NSCoding protocol, it can be saved and loaded in plists just like our basic array from before.

there are no: Dumb Questions

Q:

You keep talking about NSCoding. What is that?

A:

NSCoding is a protocol that works with the encoding and decoding of objects. Working with this protocol means dealing with how an object can be stored on disk or distributed throughout the device. For more information about NSCoding, see the Apple documentation.

Q:

Where did the back button in the detail view come from? We didn't do that...

A:

It's automatic functionality that comes with the navigation controller. When you added a title for the main view, the navigation controller kept track of that name as part of the view stack for navigation, and added a back button with the title in it. So yeah, you did do that!

Debugging—the dark side of iPhone development

Something has gone wrong, but honestly, this is a pretty normal part of the development process. There are lots of things that could cause our application to crash, so we need to figure out what the problem is.

Warnings can help find problems without debugging

In general, if your application doesn't build, Xcode won't launch it—but that's not true for warnings. Xcode will happily compile and run an application with warnings and your only indication will be a little yellow yield sign in the bottom right corner of Xcode. Two minutes spent investigating a warning can save hours of debugging time later.

3 errors and 3 warnings... the errors have to be fixed. The warnings should be investigated—and probably fixed, too.

That's not our problem, though: our code should be warning and compile-error-free. The good news is that when an app crashes in the Simulator, it doesn't go away completely (like it would on a real device). Xcode stops the app right before the OS would normally shut it down. Let's use that to see what's going on.

Time for some debugging...

First stop on your debugging adventure: the console

We need to figure out why our app crashed, and thankfully, Xcode has a lot of strong debugging capabilities. For now we're just going to look at the information it gave us about the crash, but later in the book we'll talk about some of the more advanced debugging features.

Since you ran the program in the simulator, the console should be up. Here's what ours looks like:

The toolbar contains typical debugging commands, like stopping your application, restarting it, and continuing after hitting a breakpoint.

The console has the information about what happened that caused our application to be shut down. It doesn't tell us why it happened, though...

The console tells us that our app was shut down because of an uncaught exception, and what that exception was.

The console also gives us a stack trace of where our application was, but there's a much better view of that coming up in a second...

Interact with your application while it's running

The console is a very powerful debugging tool. Some of the best debugging techniques involve well-placed logging messages using NSLog(...). This information is printed into the console and can help you diagnose problems quickly. The console isn't just read-only, though; it is your window into your running application. We'll see log messages displayed in the console, and when your application hits a breakpoint, you'll be placed at the console prompt. From there you can use debugging commands like print, continue, where, up, and down to inspect the state of your application.

The console debugger is actually the open source gdb prompt, so nearly all gdb commands work here.

And when it's about to stop running

In this case, we're dealing with a nearly dead application, but the idea is the same. Since DrinkMixer has crashed, Xcode provides you with the basic information of what went wrong. In our case, an "unrecognized selector" was sent to an object. Remember that a selector is basically a method call—it means that some code is trying to invoke methods on an object and those methods don't exist.

The console prompt lets you interact with your application at the command line.

But Xcode doesn't stop at the command line. It has a full GUI debugger built right in. Let's take a look...

Xcode supports you after your app breaks, too

So far we've used Xcode to write code and compile and launch our applications. Its usefulness doesn't stop once we hit the "Build and Debug" button. First, we can set breakpoints in our code to let us keep an eye on what's going on. Simply click in the gutter next to the line where you want to set a breakpoint. Xcode will put a small blue arrow next to the line and when your application gets to that line of code, it will stop and let you poke around using the console.

This switch indicates whether the breakpoints are on or not.

When the breakpoints are on, you'll get this cool can of bug spray icon...

Once your app hits a breakpoint, Xcode will insert Step Into, Step Over, Continue, and Debugger buttons to let you walk through your code.

To set a breakpoint, just click here.

Click on the small bug spray icon or press Shift-

Xcode supports you after your app breaks, too

The Xcode debugger shows you the state of your application

The debugger shows your code and also adds a stack view and a window to inspect variables and memory. When you click on a stack frame, Xcode will show you the line of code associated with that frame and set up the corresponding local variables. There isn't anything in the debugger window you couldn't do with the console, but this provides a nice GUI on top of it.

Here are the Step and Continue buttons to let you walk through your code.

Here's the stack from your app at the current breakpoint (or crash...). If you click on a frame, Xcode will show you the corresponding code.

Xcode shows you your app's variables (local, global, etc.) in this view.

Here's our uncaught exception for the unrecognized selector again...

What the heck is going on?

Our application is crashing, and it's not at the array loading code. Open up the debugger and click on the topmost frame that contains our code. It will show you the line that's causing the problem... see what's wrong?

To be continued...

MultipleViewscross

Take what you've learned about the navigation controller and multiple views to fill in the blanks.

Across

3. The set of views that the nav controller deals with.

6. Dictionaries use __________ to organize data.

8. The screen that gives you output from the app.

9. A template that combines a table view and nav controls.

10. Has cells that need to be customized to work.

Down

1. A more versatile way to manage data beyond an array.

2. DrinkMixer is this type of app.

4. To use a new class you need to ___________ it.

5. The @ symbol is shorthand for creating one of these.

7. A tool in Xcode to help fix broken code.

Your iPhone Toolbox

Get Head First iPhone Development now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.