2 September 2010

How To Create A Simple iPhone App Tutorial: Part 3/3

Perhaps the Scariest Bug of All!

Perhaps the Scariest Bug of All!

This article is the final part of a 3 part series on how to create a simple iPhone app for beginners. And this app happens to be about rating scary bugs!

In the first part of the series, we created an app that contained a list of bugs in a table view.

In the second part of the series, we covered how to create a detail view for the bugs.

In this article, we’ll cover how to add new bugs, how to add an icon and default image to our project, and how to handle long-running operations.

So let’s wrap this app up!

Adding and Deleting Bugs

Everything’s working great so far, but so far this isn’t a very user-friendly app! I mean the first thing anyone would want to do is add their own bug, and so far the only way to do that is by editing code!

Luckily, since we wrote our EditBugViewController and are using a UITableViewController, most of the infrastructure is already in place! There are just three changes we have to make to EditBugViewController.m, but I’m going to explain them bit-by-bit to keep things easy to understand:

1) Set up navigation bar buttons

// Inside viewDidLoad
self.navigationItem.leftBarButtonItem = self.editButtonItem;
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] 
    initWithBarButtonSystemItem:UIBarButtonSystemItemAdd 
    target:self action:@selector(addTapped:)] autorelease];

First, in viewDidLoad, we set up some buttons in the navigation bar. Just like “title” is a special property in view controllers used by the navigation controller, “navigationItem” is another of those. Whatever you set for the leftBarButtonItem and the rightBarButtonItem will show up in the navigation bar when the navigation controller shows your view controller.

For the leftBarButtonItem, we use a special built-in button called “editButtonItem.” This button says “Edit” and toggles the UITableView between edit mode (where you can delete rows for example) and normal mode.

For the rightBarButtonItem, we create a button that the user can tap to create a new bug entry. It turns out there’s already a built-in system item for adding (that looks like a + symbol), so we go ahead and use that, and register the “addTapped:” method to be called when it’s tapped.

2) Implement tableView:commitEditingStyle:forRowAtIndexPath

// Uncomment tableView:commitEditingStyle:forRowAtIndexPath and replace the contents with the following:
if (editingStyle == UITableViewCellEditingStyleDelete) {        
    [_bugs removeObjectAtIndex:indexPath.row];
    [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

This method is called when the user chooses to modify a row in some way. We check to see that the user is trying to delete the row, and if so we delete it. Note we have to remove it both from our data model (_bugs) AND notify the table view that one of the rows has been deleted, via deleteRowsAtIndexPaths.

3) Handle adding a new bug

// Add new method
- (void)addTapped:(id)sender {
    ScaryBugDoc *newDoc = [[[ScaryBugDoc alloc] initWithTitle:@"New Bug" rating:0 thumbImage:nil fullImage:nil docPath:nil] autorelease];
    [_bugs addObject:newDoc];
 
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:_bugs.count-1 inSection:0];
    NSArray *indexPaths = [NSArray arrayWithObject:indexPath];    
    [self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:YES];
 
    [self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionMiddle];
    [self tableView:self.tableView didSelectRowAtIndexPath:indexPath];
}

When the user taps the add button, we create a ScaryBugDoc with some default values, and add it to the bugs array. Note we have to also update the table view so it knows there’s a new row.

Then we call some code to make the table view act as-if the user selected the new row, so we immediately go into the edit view for the new bug.

That’s it! If you compile and run the code, you should now be able to add your own bugs, such as this one:

An Objective-C Bug

Adding An Icon and Default Image

Ok, our app is looking pretty fun and amusing, let’s ship it and get rich!

Except it would be pretty embarassing if we did right now, I mean we don’t even have an icon!

Luckily, that is quite easy to fix. Earlier, we added an icon to our project file (logo1.png), from ExtraStuffForScaryBugs.zip. Let’s set that as the icon for our project!

To do that, simply open up ScaryBugs-Info.plist, and modify the Icon file to read “logo1.png”:

Setting Icon in Info.plist

As you continue in your iOS development adventures, you’ll be coming back to Info.plist quite a bit to configure your projects in various ways.

There’s one other thing we should fix as well. If you try running ScaryBugs on your iPhone, you might notice that after you tap the icon, there’s a pause before it shows up where just a black screen displays. That is kind of embarassing behavior – it looks like the app isn’t very responsive.

According to Apple’s documentation, the best thing to do is to display a screen that looks just like your app would, but without any data in it yet. That’s pretty easy to do. Just open up RootViewController.m, and make the following change:

// Replace tableView:numberOfRowsInSection's return statement to the following:
return 0; //return _bugs.count;

Then run the project on your device, and you’ll see an empty table view after it loads. In XCode, go to Window\Organizer, click on your device, go to the screenshots tab, and click “Capture” to get a screenshot:

Taking a Screenshot with Organizer

You can find these screenshots in /Users/yourUserName/Library/Application Support/Developer/Shared/Xcode/Screenshots. Find the one you took, rename it to “Default.png”, drag it over into your Resources folder to add it to your project.

Then restore tableView:numberOfRowsInSection to the way it was, and run it on your device again, and if all works well you should see a default screen as it loads instead of a blank view!

Bonus: Halding Long-Running Operations

If you run the app on the Simulator, everything probably appears fine, but if you run it on your iPhone and go to tap a picture to change it, there is a LONG delay as the UIImagePicker initializes. After picking a picture, there is another long delay as the image is resized (especially if it’s large). This is a very bad thing, as it makes your application seem unresponsive to users.

The main rule to keep in mind is that you should never perform long-running operations on the main thread. We’re currently violating this rule in two places, which is why our app appears unresponsive.

What you should do instead is run long-running operations on a background thread. Ideally, the operation would be done in the background as the user continues to do other things. But if the work is required to occur before the user can continue (such as loading the image picker), at the very least you should display a loading indicator of some sort so the user understands that the app is working and not jsut broken.

So that’s what we’ll do here – run the long-running code on a background thread, and display a “loading” view on the foreground thread while we wait for the operation to complete.

The desire to display a loading view is a common problem for app developers, so a lot of people have created some activity indicator libraries that we can use to save ourselves some time doing it ourselves. I’ve tried a bunch of these, my current favorite is DSActivityView by David Sinclair, so let’s try that. You can download a copy off their page, or just grab a copy here.

Once you’ve downloaded DSActivityView, add the files to your project under the “Views” group. Then make the following changes to EditBugViewController.h:

// Before @interface
@class DSActivityView;
 
// Inside @interface
DSActivityView *_activityView;
NSOperationQueue *_queue;
 
// After @interface
@property (retain) DSActivityView *activityView;
@property (retain) NSOperationQueue *queue;

Here we declare our DSActivityView and something we haven’t discussed yet called an NSOperationQueue (more on this later).

Next make the following changes to EditBugViewController.m:

// At top of file
#import "DSActivityView.h"
 
// After @implementation
@synthesize activityView = _activityView;
@synthesize queue = _queue;
 
// At end of viewDidLoad
self.queue = [[[NSOperationQueue alloc] init] autorelease];
 
// In viewDidUnload
self.queue = nil;
 
// In dealloc
[_queue release];
_queue = nil;
 
// Replace addPictureTapped with the following:
- (IBAction)addPictureTapped:(id)sender {
    if (_picker == nil) {   
        [DSBezelActivityView newActivityViewForView:self.navigationController.navigationBar.superview withLabel:@"Loading Image Picker..." width:160];        
        [_queue addOperationWithBlock: ^{
            self.picker = [[UIImagePickerController alloc] init];
            _picker.delegate = self;
            _picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
            _picker.allowsEditing = NO;
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                [DSBezelActivityView removeViewAnimated:YES];
                [self.navigationController presentModalViewController:_picker animated:YES];    
            }];
        }];
    } else {
        [self.navigationController presentModalViewController:_picker animated:YES];
    }    
}
 
// Replace imagePickerController:didFinishPickingMediaWithInfo with the following:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {    
 
    [self dismissModalViewControllerAnimated:YES];
 
    [DSBezelActivityView newActivityViewForView:self.navigationController.navigationBar.superview withLabel:@"Resizing Image..." width:160];   
    [_queue addOperationWithBlock: ^{
        UIImage *fullImage = (UIImage *) [info objectForKey:UIImagePickerControllerOriginalImage]; 
        UIImage *thumbImage = [fullImage imageByScalingAndCroppingForSize:CGSizeMake(44, 44)];
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            _bugDoc.fullImage = fullImage;
            _bugDoc.thumbImage = thumbImage;
            _imageView.image = fullImage;
            [DSBezelActivityView removeViewAnimated:YES];
        }];
    }]; 
}

The first thing we do here is create an NSOperationQueue in our viewDidLoad. Details on the NSOperationQueue could easily fill an entire tutorial, so for now just think of it as an object you can submit tasks to execute to, and it will run them on a background thread for you.

Next, you’ll see that we’ve rewritten our addPictureTapped method a good bit. The first thing we do is use DSActivityView to present a loading screen by calling the newActivityForView method. We pass the navigation bar’s superview so the popup covers the entire screen, including the navigation bar.

Then we call a method on the operation queue to schedule some work to run on the background called addOperationWithBlock. Note that this is a new API only available on iPhone OS 4.0 or later (so it wouldn’t work on the iPad right now). There are other ways you can do the same thing on older OS’s, but I think this is API is pretty cool and a nice way of doing things if you can use it, so I wanted to show it off here.

Anyway, addOperationWithBlock uses a new feature of iOS called blocks. If you haven’t read up on it yet, I’d recommend checking out Apple’s excellent short practical guide to blocks.

So, everything inside our curly braces after addOperationWithBlock will be run on a background thread so the animation of the “Loading” view can continue and the user can see that the app is working away. Here we do the long-running work of initializing the image picker. When wer’e done, we need to stop the activity indicator and present the view controller – but we need to do that on the main thread.

Why? Well the rule of thumb is any time you need to modify the UI, you need to do that on the main thread. And we can use a special built-in NSOperationQueue called the “mainQueue” to queue up another block to run on the main thread once the other work is complete.

We follow the same idea in imagePickerController:didFinishPickingMediaWithInfo.

And that’s it! Give it a run on your device, and you’ll see a new animation while the long-running work takes place, which makes for a much nicer user experience.

Loading Indicator with DSActivityView

Where To Go From Here?

Here is a sample project with all of the code we’ve developed in this tutorial series.

Please let me know if anything in the above is confusing or if you’d like me to go into more detail about anything.

Guess what – this isn’t the end for these bugs! We’ll be extending this project even more in an upcoming tutorial series on persisting app data to disk, implementing the new iTunes File Sharing feature, and sharing app data via email! This new series should be interest to any iOS developers from beginner to advanced – so stay tuned!

Tags: , ,

Leave a Comment!

31 August 2010

How To Create A Simple iPhone App Tutorial: Part 2/3

Apparently this Lady Bug isn't very scary!

Apparently this Lady Bug isn't very scary!

This article is the second part of a 3 part series on how to create a simple iPhone app for beginners. And this app happens to be about rating scary bugs!

In the first part of the series, we created an app that contained a list of bugs in a table view.

In this article, we’ll cover how to create a detail view so that we can view a larger picture of the bugs, rate them, and and change their pictures!

In the third and final part of the series, we’ll cover how to add new bugs, add an icon and default image to our project, and handle long-running operations.

So let’s get to making some bugs! After all, isn’t that what programming’s all about? :]

View Controllers, Oh My!

Now that we have a list of bugs, it would be nice to be able to tap on the bug to bring up a screen where we can edit the bug’s name or picture, and rate the bug.

Most of the time in iPhone apps, for every “screen” of the app you have a class that is the “View Controller” for that screen. Right now we already have one view controller for our table view (RootViewController), so now we’ll need a second for our details screen.

Each “View Controller” can contain multiple views. In our table view controller, we just had a single view – the table view. However in our details view controller, we’re going to need a bunch of views – we’re going to need a view for the name of the bug, a view for the image, a view for the rating, and several others.

Download Some Stuff!

Speaking of which – we’re going to need a 5-star rating view in this details screen – but the iPhone doesn’t come with one by default. However, I recently wrote a tutorial on How To Make a Custom UIView: A 5-Star Rating View, so we’ll just use the view from that tutorial again here.

Don’t worry about going throught that tutorial now (unless you feel like it) – instead you can just download the Extra Stuff for Scary Bugs package that I put together for this project.

Go ahead and download the file then:

  • Create a new group named “Views” in XCode, and drag RateView.h/RateView.m to that group, making sure “Copy items into destination group’s folder (if needed)” is checked. This is the 5-star rating view code from the tutorial.
  • Repeat for UIImageExtras, except drag them to a new group named “Helpers”. This is some helper code we’ll need to resize images a bit later on.
  • Repeat for the three shocked face images made by my lovely wife, except drag them to the Resources group instead. These are the images we’ll be using for the “stars” in our rating view for some humorous flair :]
  • Repeat for the logo1.png, also drag that to the Resources group. We’ll be setting that as the app icon later.

Creating Our View Controller With Interface Builder

Ok – now we’re finally ready to go! Right click on the View Controllers group, and click “Add\New File…”. Choose Cocoa Touch Class\UIViewController subclass, and make “With XIB for user interface” is checked but no others are:

Create View Controller

Click Next, name the file EditBugViewController.m, and click Finish.

You’ll see that three new files should be added to your project – EditBugViewController.h, EditBugViewController.m, and EditBugViewController.xib.

So what’s this XIB thing all about? Well double click it to find out:

Opened XIB in Interface Builder

A XIB is the file type that you edit with Interface Builder – a visual way to construct your user interface in XCode. You drag and drop UI elements onto your view, set up their properties the way you want, and you can even connect the elements to properties in your View Controller classes.

The easiest way to understand it is to try it out! But before we do, we need to set up some properties in our EditBugViewController.h first. So replace EditBugViewController.h with the following:

#import <UIKit/UIKit.h>
#import "RateView.h"
 
@class ScaryBugDoc;
 
@interface EditBugViewController : UIViewController <UITextFieldDelegate, RateViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate> {
    ScaryBugDoc *_bugDoc;
    UITextField *_titleField;
    UIImageView *_imageView;
    RateView *_rateView;
    UIImagePickerController *_picker;
}
 
@property (retain) ScaryBugDoc *bugDoc;
@property (retain) IBOutlet UITextField *titleField;
@property (retain) IBOutlet UIImageView *imageView;
@property (retain) IBOutlet RateView *rateView;
@property (retain) UIImagePickerController *picker;
 
- (IBAction)titleFieldValueChanged:(id)sender;
- (IBAction)addPictureTapped:(id)sender;
 
@end

You may notice some funky types above – IBOutlet and IBAction. These are “magic keywords” that Interface Builder looks for, in order to allow us to associate controls that we add in interface builder to properties on our class. Basically, if we put an IBOutlet or IBAction next to a property/method, Interface Builder will detect it so we can hook it up later. These things are called “outlets” btw.

Now that our class and outlets are ready, let’s set up our XIB. In the window that reads “EditBugViewController.xib”, double click the View to bring up the View window if it isn’t there already. Then go to “Layout\Show Bounds Rectangles”, which will make it a bit easier to lay out the elements.

Now, let’s start adding some controls onto the screen! Bring up the Library by going to Tools\Library, and drag a UITextField, UIImageView, and a UIView onto the text screen and arrange them like the following (the text field is on top):

Interface Builder Layout

Then select the UITextField and click Tools\Attributes Inspector so we can change some properties.

Set the font to Helvetica-Bold size 18.0, the text alignment to center, the clear button behavior to “Appears While Editing”, the capitalization to Words like the following:

UITextField Attributes

Then, switch over to the Size Inspector by clicking on the third tab or going to Tools\Size Inspector, and set up the autosizing attributes like the following:

Autosizing Attributes

This will make it so that when our view gets rotated to landscape, the text field stretches across the screen to become wider.

Next lets set up our UIImageView. In the first tab (Attributes Inspector) set the mode to “Aspect Fit”, and in the third tab (Size Inspector) and set the autosizing attributes to the following:

Autosizing Attributes

For the UIView, go to the fourth tab (Identity Inspector) and set the set the Class Identity to “Rate View” so our 5-Star Rating view shows up there. Then in the third tab (Size Inspector) set the autosizing attributes to the following:

Autosizing Attributes

So far, so good! Next we need to add a few more controls to the screen so that the user can tap the UIImageView area to change the picture.

There are a couple ways we can do this, but one easy way it to create an invisible button on top of the UIImageView, and set it up so that we get a callback when the button is tapped. We can also add a UILabel underneath the picture to say “Tap to Change Picture” if there is no picture set.

So drag a UIButton from the library, and resize it to be the same exact size and postion as the UIImageView. To make it invisible, in the first tab (Attributes Inspector) change the type to Custom. Then in the third tab (Size Inspector) set the autosizing attributes to the following:

Autosizing Attributes

Finally, drag a UILabel from the library, place it in the middle of the UIImageView, and double click to edit the text, changing it to “Tap To Change Image.” Then change the text alignment to center. Also, drag the UILabel up a few positions in the XIB so it’s behind the UIImageView (the list goes from bottom to top):

XIB Order for Scary Bugs

Then in the third tab (Size Inspector) set the autosizing attributes to the following:

Autosizing Attributes

Before you move on, you can double check that you’ve gotten all of the autosizing attributes right by selecting the root View, and changing the orientation from Portrait to Landscape:

Landscape Test

If something isn’t right, don’t worry – just change it back to Portrait and double check the settings.

Phew! We’ve added all of the controls we need, so all we need to do now is hook everything up to their outlets in our class:

  • Control-drag from “File’s Owner” to the UITextField, RateView, and UIImageView, and connect each of them to their corresponding outlets.
  • Control-drag from the UITextField back up to the “File’s Owner” to set the EditBugViewController as the delegate of the UITextField.
  • Control-drag from the UIButton back up to the “Files’s Owner”, and connect it to the “addPictureTapped” outlet. This is a shortcut for hooking up the TouchUpInside event to that outlet.
  • Right click on UITextField, drag from the dot next to “Editing Changed” up to “File’s Owner”, and connect the action to the “titleFieldValueChanged” outlet.

When you’re done you can double check your work by right clicking on File’s Owner to bring up a popup with all of the connections you’ve set, it should look similar to the following:
Files Owner Connections

Implementing Our Detail View

We’ve got the Interface Builder side all set up – now we need to finish impilementing EditBugViewController.m and hook it up to the rest of the project.

We’re going to make a bunch of changes to EditBugViewController.m. There’s a lot of code here, so let’s go over it party by part.

1) Import headers and synthesize properties

// At top of file
#import "ScaryBugDoc.h"
#import "ScaryBugData.h"
#import "UIImageExtras.h"
 
// After @implementation
@synthesize bugDoc = _bugDoc;
@synthesize titleField = _titleField;
@synthesize imageView = _imageView;
@synthesize rateView = _rateView;
@synthesize picker = _picker;

You should be a pro at this by this point!

2) Set up Rate View

// Uncomment viewDidLoad and add the following inside
_rateView.notSelectedImage = [UIImage imageNamed:@"shockedface2_empty.png"];
_rateView.halfSelectedImage = [UIImage imageNamed:@"shockedface2_half.png"];
_rateView.fullSelectedImage = [UIImage imageNamed:@"shockedface2_full.png"];
_rateView.editable = YES;
_rateView.maxRating = 5;
_rateView.delegate = self;

In viewDidLoad, we set up the properties of our RateView. For more details, check out the How To Make a Custom UIView: A 5-Star Rating View tutorial.

3) Enable autorotation

// Uncomment shouldAutorotateToInterfaceOrientation and replace the return value to:
return YES;

In shouldAutorotateToInterfaceOrientation, we return YES since we went to all the work of setting up the autosizing attributes in Interface Builder! This will allow the user to rotate this view between orientations, and our controls will re-layout according to the autosizing attributes we set up.

4) Release memory properly where applicable

// Inside didReceiveMemoryWarning
self.picker = nil;
 
// Inside viewDidUnload
self.titleField = nil;
self.imageView = nil;
self.rateView = nil;
 
// Inside dealloc
[_bugDoc release];
_bugDoc = nil;
[_titleField release];
_titleField = nil;
[_imageView release];
_imageView = nil;
[_rateView release];
_rateView = nil;
[_picker release];
_picker = nil;

In didReveiveMemoryWarning, we should clear out anything that we may have a pointer to that we don’t absolutely need because we can recreate it later. In this case, the Image Picker fits into that category becuase we will write our code so that we re-create it wheneer its nil.

viewDidUnload is called when the OS is trying to save memory – one of the things it does is unload any views that are no longer visible. When they become visible again, viewDidLoad is called again. So in viewDidUnload, it’s important to set anything we create in viewDidLoad to nil, as well as anything that was connected by Interface Builder. So here we set our three controls connected by Interface Builder to nil.

And of course, in dealloc, we should free everything.

5) Set up initial UI state

// Add new method
- (void)viewWillAppear:(BOOL)animated {    
    _titleField.text = _bugDoc.data.title;
    _rateView.rating = _bugDoc.data.rating;    
    _imageView.image = _bugDoc.fullImage;
    [super viewWillAppear:animated];
}

viewWillAppear is a good place to initialize all of our controls to what they should display, based on our model.

6) Handle text view and rating view

- (IBAction)titleFieldValueChanged:(id)sender {
    _bugDoc.data.title = _titleField.text;
}
 
#pragma mark UITextFieldDelegate
 
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [textField resignFirstResponder];
    return YES;
}
 
#pragma mark RateViewDelegate
 
- (void)rateView:(RateView *)rateView ratingDidChange:(float)rating {
    _bugDoc.data.rating = rating;
}

We set up titleFieldValueChanged to be called whenever the user changes the value of the text field, so we update the model as well whenever it changes.

textFieldShouldReturn is called when the user hits the return key on the keyboard. We call resignFirstResponder to get the keyboard to disappear off the screen when that happens.

rateView:ratingIsChanged is called when the user chooses a new rating since we set ourselves as the RateView’s delegate, so when that happens we update our model.

In case you were wondering, the #pragma marks are just special lines that XCode can read to set up separators in the editor’s function list for organization’s sake:

Pragma Mark Headers

7) Display image picker and process results

- (IBAction)addPictureTapped:(id)sender {
    if (_picker == nil) {   
        self.picker = [[UIImagePickerController alloc] init];
        _picker.delegate = self;
        _picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
        _picker.allowsEditing = NO;    
    } 
    [self.navigationController presentModalViewController:_picker animated:YES];    
}
 
#pragma mark UIImagePickerControllerDelegate
 
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    [self dismissModalViewControllerAnimated:YES];
}
 
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {    
 
    [self dismissModalViewControllerAnimated:YES];
 
    UIImage *fullImage = (UIImage *) [info objectForKey:UIImagePickerControllerOriginalImage]; 
    UIImage *thumbImage = [fullImage imageByScalingAndCroppingForSize:CGSizeMake(44, 44)];
    _bugDoc.fullImage = fullImage;
    _bugDoc.thumbImage = thumbImage;
    _imageView.image = fullImage;
}

We set up addPictureTapped to be called whenever the user taps the invisible button above the UIImage, so here we create the UIImagePicker (if it doesn’t exist already), and set the photo source to photo library (you can choose other things such as camera as well). We set ourselves as the delegate so we can get callbacks when the user finished picking the picture. Finally, we present the image picker as a modal view controller, which means it takes up the whole screen.

Finally, we implement the image picker callbacks for when the user picks an image or cancels. Either way, we dismiss the modal view controller. If the user did pick the image, we get the full image and also a thumbnail version (which we resize with the UIImageExtras class that we added earlier), and update both the model and the view.

OMG – you’re probably sick of writing code now eh? Don’t worry – we’re almost done, just gotta hook this baby in!

Integrating Our Detail View

This should be pretty quick. Make the following changes to RootViewController.h:

// Before @interface
@class EditBugViewController;
 
// Inside @interface
EditBugViewController *_editBugViewController;
 
// After @interface
@property (retain) EditBugViewController *editBugViewController;

We’re just declaring an instance variable and property to keep track of the view controller for editing a bug.

Then make the following changes to RootViewController.m:

// At top of file
#import "EditBugViewController.h"
 
// In synthesize section
@synthesize editBugViewController = _editBugViewController;
 
// Uncomment viewWillAppear and add the following inside:
[self.tableView reloadData];
 
// Add inside tableView:didSelectRowAtIndexPath
if (_editBugViewController == nil) {
    self.editBugViewController = [[[EditBugViewController alloc] initWithNibName:@"EditBugViewController" bundle:[NSBundle mainBundle]] autorelease];        
}
ScaryBugDoc *doc = [_bugs objectAtIndex:indexPath.row];
_editBugViewController.bugDoc = doc;
[self.navigationController pushViewController:_editBugViewController animated:YES]; 
 
// Inside didReceiveMemoryWarning
self.editBugViewController = nil;
 
// Inside dealloc
[_editBugViewController release];
_editBugViewController = nil;

Most of this should be self-explanatory, however there are two things to point out.

First, note that in viewWillAppear, we reload the data in the table. This is because when the user is in the detail view, they might change the name of the bug or the picture, and we want the updated name/picture to show up when they come back to the table view. One easy way to do that is to reload the entire table, so we do that here.

The important code is in the tableView:didSelectRowAtIndexPath method. When the user selects a row, we check to see if we’ve created the editBugViewController, and if not we go ahead and create it. Then we get the bug the user has tapped on by indexing the _bugs array, set the bug property on the editBugViewController, and present the view controller by pushing it onto the navigation stack.

Finally, we’re done! Go ahead and compile and run your project, and if all goes well you should now be able to bring up the bug detail view, change the names of the bugs, change their pictures, rate them, and even rotate to any orientation!

Detail View Controller Example

Where To Go From Here?

Here is a sample project with all of the code we’ve developed so far in this tutorial series.

Please let me know if anything in the above is confusing or if you’d like me to go into more detail about anything.

Next in the series, we’ll cover how to add and delete bugs, add an icon and default image to our project, and correctly handle long-running operations!

Tags: , , ,

2 Comments

25 August 2010

How To Create A Simple iPhone App Tutorial: Part 1/3

iPhone programming is like a ladybug - fun and only a little scary!

iPhone programming is like a ladybug - fun and only a little scary!

The iPhone is an amazing platform to develop on for indie software developers. It’s never been easier to come up with your own unique app idea, code something up, and have it be available to millions of potential customers!

Lately I’ve been getting a lot of questions from people new to iOS development asking how to get started. So I thought it would be helpful to write a tutorial series tailored for beginners.

But rather than focusing in detail on just one topic, we’re going to dive in and create an entire functional app from scratch. By the end, you’ll have tried out many areas of iPhone development, and ready to dig in further.

So what’s the app we’re going to make? Well, there’s a story behind that…

The other night, I saw a picture of a Potato Bug for the first time and started freaking out because it was so big and ugly! Then I got obsessed with looking up all kinds of scary bug pictures online. So to spread the fun, we’re going to make an app for that – rating scary bugs!

While making this app, we’ll cover some of the most commonly used topics in iPhone development:

  • What You Need to Get Started with iPhone Development
  • How to store your app data in a Model
  • How to use Table Views – including adding and deleting rows
  • How to create a detail view for a row
  • How to support both Portrait & Landscape orientations
  • How to use Navigation Controllers
  • How to use an Image Picker
  • How to use common controls such as a text field, button, and image view
  • How to add icons and default images
  • Bonus: How to handle long-running operations

It sounds like a lot, but don’t get scared – we’re not afraid of no bugs!

In this first part of this three part series, we’ll cover how to load our model with a list of bugs and display them in a table view.

This tutorial is for beginner iOS developers, however it assumes you are familiar with Objective-C and programming in general. If you are new to Objective-C, I recommend reading Apple’s Objective-C Programming Language Guide first.

What You Need

First things first – to develop for the iPhone, you’ll need a Mac. Pretty much any Mac will do, as long as it’s powerful enough to run the latest version of the Mac OS, Snow Leopard. But if you’re looking to go the cheap route, you can pick up a Mac Mini for relatively cheap, and it works just fine for a development machine.

Next, you’ll need to get a copy of XCode, Apple’s IDE for iOS development. So if you haven’t already, register for a free account at the iPhone Dev Center and download a copy of XCode.

If you’d like, you can sign up for the paid iPhone Developer Program that allows you to distribute your apps on the App Store, but if you just want to try out iOS development the free account works fine.

If you get serious about iOS development, you’ll probably want physical device(s) (iPhone/iPhone 4/iPod Touch/iPad) as well. It’s true that you can do a lot of testing with just the Simulator, but there are some APIs that don’t work on the Simulator, and you’ll need a physical device for performance testing.

That’s it – so if you haven’t already, grab a copy of XCode, fire it up, and let’s continue on!

Hello, Table View!

We’re going to start out by using one of the most common controls on the iPhone – the Table View. You’ve probably seen the Table View in a lot of apps already, here are a few examples:

UITableView Examples

So anyway, our first screen in the app will have one of these, to display a list of scary bugs!

Let’s get started by going to File\New Project in XCode, choose Application under iPhone OS, Navigation-based Application from the list on the right, and click “Choose…”:

Navigation Based App

Name the project ScaryBugs and click Save. And before we do anything else, let’s check out what we’ve got so far! In the toolbar at the top of the screen pick Simulator from the list, then go to Build\Build and Run. If all goes well, you should see the following in your simulator:

Empty Table View

So as you can see, we already have a working project to start from since we chose the Navigation-based Application template. We’re not going to dig into the template since that’s beyond the scope of this tutorial, but just notice that we have an empty table view all set up for us and ready to go – we just have to fill it in with data!

So to do that, let’s create a class to keep track of our scary bugs.

A Scary Data Model: Organization

Notice how there’s a hierarchy of folders in the Groups & Files section of XCode:

Groups And Files for our Project

The template comes set up with several groups – Classes, Other Sources, Resources, etc. These groups are just for organizational purposes, so feel free to change them however you want. In our case, we’re going to have a fair number of files in this project, so let’s organize things a bit.

First, rename the existing group named “Classes” to “View Controllers”. You can do this by right clicking on the group, clicking “Rename”, and naming it “View Controllers.”

Then right click on the “ScaryBugs” project under Groups & Files, click “Add\New Group”. Name the Group “Model”, because we’re about to add couple classes for our data model there.

Before we begin, let’s talk about how we’re going to organize things:

  1. ScaryBugData: Contains bug name and rating.
  2. ScaryBugDoc: Contains full size image, thumbnail image, ScaryBugData.

The reason we’re setting things up like that is it will make things easier in the follow-up for this tutorial, where we’re going to start saving our data to the disk, implementing file sharing, and the like.

A Scary Data Model: Implementation

Ok so let’s do it! Right click on the Model group and click “Add\New File…”. Under iPhone OS, choose “Cocoa Touch Class”, then “Objective-C class”, make sure “Subclass of NSObject” is selected, and click Next:

Add New File

Name the file ScaryBugData.m, make sure “Also create ScaryBugData.h” is checked, and click Finish. If all went well, your Groups & Files should now look similar to this:

Organized Groups and Files

Ok, time to create our ScaryBugData class. Replare ScaryBugData.h with the following:

#import <Foundation/Foundation.h>
 
@interface ScaryBugData : NSObject {
    NSString *_title;
    float _rating;
}
 
@property (copy) NSString *title;
@property  float rating;
 
- (id)initWithTitle:(NSString*)title rating:(float)rating;
 
@end

This is pretty simple stuff – we’re just declaring an object with two instance variables – a string for the name of the bug, and a float for how scary we rated it.

We also declare a property for each of the instance variables, and define an initializer for the class.

Switch over to ScaryBugData.m and replace it with the following:

#import "ScaryBugData.h"
 
@implementation ScaryBugData
@synthesize title = _title;
@synthesize rating = _rating;
 
- (id)initWithTitle:(NSString*)title rating:(float)rating {
    if ((self = [super init])) {
        _title = [title copy];
        _rating = rating;
    }
    return self;
}
 
- (void)dealloc {
    [_title release];
    _title = nil;    
    [super dealloc];
}
 
@end

Again, extremely simple stuff here. We synthesize our properties, create our initializer to fill in our instance variables from the passed-in parameters, and release our variables in dealloc.

Ok that’s it for ScaryBugData. Now follow the same steps you did above to create another subclass of NSObject, this time named ScaryBugDoc.

Replace ScaryBugDoc.h with the following:

#import <Foundation/Foundation.h>
 
@class ScaryBugData;
 
@interface ScaryBugDoc : NSObject {
    ScaryBugData *_data;
    UIImage *_thumbImage;
    UIImage *_fullImage;
}
 
@property (retain) ScaryBugData *data;
@property (retain) UIImage *thumbImage;
@property (retain) UIImage *fullImage;
 
- (id)initWithTitle:(NSString*)title rating:(float)rating thumbImage:(UIImage *)thumbImage fullImage:(UIImage *)fullImage;
 
@end

Nothing of particular note here – just creating some instance variables/properties and an initializer.

Replace ScaryBugDoc.m with the following:

#import "ScaryBugDoc.h"
#import "ScaryBugData.h"
 
@implementation ScaryBugDoc
@synthesize data = _data;
@synthesize thumbImage = _thumbImage;
@synthesize fullImage = _fullImage;
 
- (id)initWithTitle:(NSString*)title rating:(float)rating thumbImage:(UIImage *)thumbImage fullImage:(UIImage *)fullImage {   
    if ((self = [super init])) {
        _data = [[ScaryBugData alloc] initWithTitle:title rating:rating];
        _thumbImage = [thumbImage retain];
        _fullImage = [fullImage retain];
    }
    return self;
}
 
- (void)dealloc {
    [_data release];
    _data = nil;   
    [_fullImage release];
    _fullImage = nil;
    [_thumbImage release];
    _thumbImage = nil;
    [super dealloc];
}
 
@end

And that’s it – our data model is complete! Time to create some sample data and display it in the table view.

A Different Kind of Bug List

First, let’s set up our table view so it can handle displaying a list of ScaryBugDocs. We’ll store our ScaryBugDocs in a NSMutableArray, the collection class that you use for arrays that should be able to dynamically change in size.

Make the following changes to RootViewController.h:

// Inside @interface
NSMutableArray *_bugs;
 
// After @interface
@property (retain) NSMutableArray *bugs;

This will be the instance/variable property that we’ll use to keep track of our list of bugs.

Now go over to RootViewController.m and make the following changes:

// At top of file
#import "ScaryBugDoc.h"
#import "ScaryBugData.h"
 
// After @implementation
@synthesize bugs = _bugs;
 
// Uncomment viewDidLoad and add the following inside
self.title = @"Scary Bugs";
 
// Uncomment shouldAutorotateToInterfaceOrientation, and modify the return statement to:
return YES;
 
// Replace the return statement in tableView:numberOfRowsInSection with the following:
return _bugs.count;
 
// Inside tableView:cellForRowAtIndexPath, add the following after "Configure the cell" but before return cell:
ScaryBugDoc *doc = [_bugs objectAtIndex:indexPath.row];
cell.textLabel.text = doc.data.title;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.imageView.image = doc.thumbImage;
 
// Add inside dealloc
[_bugs release];
_bugs = nil;

Ok, finally something interesting to discuss!

First, note that we set a property on ourselves called “title” to the string “Scary Bugs.” “title” is a special built-in property on view controllers. When a Navigation Controller displays a view controller, it shows whatever is in the “title” property in the title bar. So by setting this, we should see “Scary Bugs” up top!

Next, note we return yes in shouldAutorotateToInterfaceOrientation, which tells the OS that we should be able to support all orientations – Portrait, Landscape, and the updside down versions of each. Since this class is a UITableViewController, that’s all we have to do – the view will rotate automatically from there!

Next, when constructing a table view you always have to override numberOfSectionsInTableView and numberOfRowsInSection to tell the OS how many sections/rows should be displayed in the table view. We just have 1 section, so we don’t have to do anything because the template is already set up to return 1 section. For the rows, we just return the number of objects in our bugs array.

Finally, we implement tableView:cellForRowAtIndexPath, which is probably the most important method to implement when making a table view. Here, you set up the cell that will be displayed for a particular row. The OS will call this method once per row for each row so you can set it up.

Let’s take a look at the entire method, since this is particularly important:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    static NSString *CellIdentifier = @"Cell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
 
    // Configure the cell.
    ScaryBugDoc *doc = [_bugs objectAtIndex:indexPath.row];
    cell.textLabel.text = doc.data.title;
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    cell.imageView.image = doc.thumbImage;
 
    return cell;
}

The first two lines call a helper function called “dequeueReusableCellWithIdentifier” to try to return a reusable cell. What is this all about?

Well, it’s an important performance optimization. Keep in mind that table views can contain a very large number of rows, but only a certain number of them are displayed on screen at a time. So rather than creating a new cell each time a new row cycles into the screen, the OS can improve performance by re-using a cell that was already created, but scrolled off-screen.

So that’s what the dequeueReusableCellWithIdentifier call is. If there’s not a reusable cell available, we just create a new cell. You can create your own table view cells, or use a standard one. In our case, the default table view works fine, so we just pick that by setting the style to UITableViewCellStyleDefault.

If you’re curious what the different standard table view cell options look like, check out the “Standard Styles for Table-View Cells” section in the Table View Programming Guide.

Finally, we configure the cell by setting its textLabel and imageView (which are available with the default style).

Believe it or not that’s all we need to do! Now we just need to set up some sample data for the table view to display.

Scary Bug Pictures!

But of course we’ll need some scary bug pictures for that! You can either browse the Internet and find some, or download these Scary Bug Pictures I found on stock.xchng.

Once you’ve downloaded the files or gotten your own, drag them all into the Resources group of your XCode project. When the popup appears, make sure “Copy items into destination group’s folder (if needed)” is checked, and click Add.

Add Resources

Then open up ScaryBugsAppDelegate.m and make the following changes:

// At top of file
#import "ScaryBugDoc.h"
#import "RootViewController.h"
 
// At beginning of application:didFinishLaunchingWithOptions
ScaryBugDoc *bug1 = [[[ScaryBugDoc alloc] initWithTitle:@"Potato Bug" rating:4 thumbImage:[UIImage imageNamed:@"potatoBugThumb.jpg"] fullImage:[UIImage imageNamed:@"potatoBug.jpg"]] autorelease];
ScaryBugDoc *bug2 = [[[ScaryBugDoc alloc] initWithTitle:@"House Centipede" rating:3 thumbImage:[UIImage imageNamed:@"centipedeThumb.jpg"] fullImage:[UIImage imageNamed:@"centipede.jpg"]] autorelease];
ScaryBugDoc *bug3 = [[[ScaryBugDoc alloc] initWithTitle:@"Wolf Spider" rating:5 thumbImage:[UIImage imageNamed:@"wolfSpiderThumb.jpg"] fullImage:[UIImage imageNamed:@"wolfSpider.jpg"]] autorelease];
ScaryBugDoc *bug4 = [[[ScaryBugDoc alloc] initWithTitle:@"Lady Bug" rating:1 thumbImage:[UIImage imageNamed:@"ladybugThumb.jpg"] fullImage:[UIImage imageNamed:@"ladybug.jpg"]] autorelease];
NSMutableArray *bugs = [NSMutableArray arrayWithObjects:bug1, bug2, bug3, bug4, nil];
RootViewController *rootController = (RootViewController *) [navigationController.viewControllers objectAtIndex:0];
rootController.bugs = bugs;

Here we just use the ScaryBugDoc initializer to create four sample bugs, passing in the title, rating, and images for each. We add them all to a NSMutableArray, and set them on our table view.

Speaking of which, we can get a pointer to the RootViewController since we know it’s the first view controller in the navigation controller’s stack. There are other ways we could have gotten a pointer as well, but this is one easy way.

And that’s it! Compile and run your app, and if all works well, you should see a list of (mostly) frightening bugs in your table view!

Scary Bugs Table View

Where To Go From Here?

Here is a sample project with all of the code we’ve developed so far in this tutorial series.

Please let me know if anything in the above is confusing or if you’d like me to go into more detail about anything.

Next in the series, we’ll cover how to create a detail view for the bugs so we can edit and rate our bugs!

Tags: , , ,

10 Comments