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

Ray Wenderlich

This post is also available in: Chinese (Simplified), French, Japanese, Spanish, Korean

Perhaps the Scariest Bug of All!

Perhaps the Scariest Bug of All!

Update 6/02/14: Fully updated for iOS 7 by Jorge Jordán.

This article is the final part of a 3 part iOS tutorial 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, you created an app that contained a list of bugs in a table view.

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

In this article, you’ll know how to add new bugs, how to add an icon and default image to your project, and how to handle long-running operations.

So 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 you wrote your RWTDetailViewController to allow editing bugs, and are using a UITableViewController for the RootViewController, most of the infrastructure is already in place! There are just four changes you have to make, but I’m going to explain them bit-by-bit to keep things easy to understand:

1) Set up navigation bar buttons

Inside RWTMasterViewController.m, add the following lines of code to viewDidLoad:

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

Here you 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, you 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, you 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 you go ahead and use that, and register the addTapped: method to be called when it’s tapped.

Note that you can also set these up in the Storyboard Editor, but I set them up here in code to show you that you can do things this way too.

2) Implement tableView:commitEditingStyle:forRowAtIndexPath

Still in RWTMasterViewController.m, replace the contents of the method tableView:commitEditingStyle:forRowAtIndexPath 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. You check to see that the user is trying to delete the row, and if so you delete it. Note you have to remove it both from your data model (_bugs) and notify the table view that one of the rows has been deleted, via deleteRowsAtIndexPaths.

3) Handle adding a new bug

Next add this new method to RWTMasterViewController.m:

- (void)addTapped:(id)sender {
    RWTScaryBugDoc *newDoc = [[RWTScaryBugDoc alloc] initWithTitle:@"New Bug" rating:0 thumbImage:nil fullImage:nil];
    [_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 performSegueWithIdentifier:@"MySegue" sender:self];    
}

You set things up in step 1 so that when the user taps the add button, this method gets called.

Here you create a RWTScaryBugDoc with some default values, and add it to the bugs array. Note you have to also update the table view so it knows there’s a new row.

Then you call some code to make the table view act as-if the user selected the new row, so you immediately go into the edit view for the new bug. You first select the row in the table view, then you manually perform the segue that you set up in the Storyboard Editor.

Note that you never actually named the segue MySegue in the Storyboard Editor earlier, so do that as your final step.

4) Name the segue

Open Main.storyboard and click on the arrow with an icon between the master and detail view controller – this is your segue.

Then in the fourth tab on the sidebar (the Attributes Inspector), set the Identifier to MySegue. This way you can manually run it when you want from code.

Naming a segue in Xcode

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, your app is looking pretty fun and amusing, time to ship it and get rich!

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

Luckily, that is quite easy to fix. Earlier, you added an icon to your project file (logo1.png), from ExtraStuffForScaryBugs2.zip. Set that as the icon for your project!

To do that, the easiest way is to select your ScaryBugs project in the Project navigator, and select your ScaryBugs target. Make sure the General tab is selected, and scroll down to App Icons and click on the arrow. Drag logo1.png from the project navigator to the iPhone Spotlight iOs 7 40pt slot:

Changing the icon for an app in Xcode

Accept any warnings, and you should see it show up. If you get a warning that the image size doesn’t match, resize logo1.png to 80×80 (an earlier download had the file larger than that).

Go ahead and delete and re-install the app on your simulator or phone, and you should see the new icon show up!

New icon for Scary Bugs

There’s one other thing you should fix as well. If you try running ScaryBugs, 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 RWTMasterController.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 the simulator, and you’ll see an empty table view after it loads. In the main menu of iOS Simulator, click on File\Save Screen Shot and find it in your desktop, you will use it as your launch image. To do that, back to XCode, select your ScaryBugs project in the Project navigator, and select the ScaryBugs target. Make sure the General tab is selected, and scroll down to Launch Images and click on the arrow. Drag the screenshot on your desktop to the iPhone Portrait iOS 7 2x.

Setting the launch image in the Xcode project settings

Then restore tableView:numberOfRowsInSection to the way it was and run the app again. You should see a default screen as it loads instead of a blank view, and the app should feel a lot more responsive!

Bonus: Handling 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. You’re currently violating this rule in two places, which is why your 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 just broken.

So that’s what you’ll do here – run the long-running code on a background thread, and display a loading view on the foreground thread while you 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 you can use to save yourself some time doing it yourself. I’ve tried a bunch of these, my current favorite is SVProgressHUD by Sam Vermette, so try that. You can download a copy off the SVProgressHUD Github page.

Once you’ve downloaded SVProgressHUD, add SVProgressHUD.h and SVProgressHUD.m to your project under the Views group. You also have to perform two configuration steps:

  1. Add required library. Click your project in the Project Navigator and select your ScaryBugs target. Select the Build Phases tab and expand the Link Binary With Libraries section. Click the + button and add QuartzCore.framework.

Adding required QuartzCore.framework library in Xcode

At this point you should be able to build your project without errors.

Then make the following changes to RWTDetailViewController.m:

// At top of file
#import "SVProgressHUD.h"
 
// Replace addPictureTapped with the following:
- (IBAction)addPictureTapped:(id)sender {
    if (self.picker == nil) {
 
        // 1) Show status
        [SVProgressHUD showWithStatus:@"Loading picker..."];
 
        // 2) Get a concurrent queue form the system
        dispatch_queue_t concurrentQueue =
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
        // 3) Load picker in background
        dispatch_async(concurrentQueue, ^{
 
            self.picker = [[UIImagePickerController alloc] init];
            self.picker.delegate = self;
            self.picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
            self.picker.allowsEditing = NO;
 
            // 4) Present picker in main thread
            dispatch_async(dispatch_get_main_queue(), ^{
                [self presentViewController:_picker animated:YES completion:nil];
                [SVProgressHUD dismiss];
            });
 
        });
 
    }  else {
        [self presentViewController:_picker animated:YES completion:nil];
    }
}

There’s a lot of new ideas here, so you’ll go over this section by section.

  1. Here you use the SVProgressHUD helper class you just added to show a “Loading” GUI with a spinner on the screen. This way the user knows some work is going on and that the app hasn’t just locked up.
  2. You want to load the image picker in the background. You can do this on iOS with a technology called Grand Central Dispatch. You won’t go over it in huge detail here (but if you are curious, read this iOS tutorial). For now, all you need to know is this line gives you a queue that you can use to run blocks of code in the background.
  3. This line executes a block of code to load the image picker in the background. If you are confused about the syntax of blocks, check out this iOS tutorial.
  4. Finally, you present the picker on the main queue. Note you must always update the GUI on the main queue – you can’t do it on a background thread.

Similarly, you can perform the image resizing in the background as well. Replace imagePickerController:didFinishPickingMediaWithInfo with this:

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
 
    UIImage *fullImage = (UIImage *) [info objectForKey:UIImagePickerControllerOriginalImage];
 
    // 1) Show status
    [SVProgressHUD showWithStatus:@"Resizing image..."];
 
    // 2) Get a concurrent queue form the system
    dispatch_queue_t concurrentQueue =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
    // 3) Resize image in background
    dispatch_async(concurrentQueue, ^{
 
        UIImage *thumbImage = [fullImage imageByScalingAndCroppingForSize:CGSizeMake(44, 44)];
 
        // 4) Present image in main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            self.detailItem.fullImage = fullImage;
            self.detailItem.thumbImage = thumbImage;
            self.imageView.image = fullImage;
            [SVProgressHUD dismiss];
        });
 
    });
 
    [self dismissViewControllerAnimated:YES completion:nil];
 
}

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.

Using SVProgressHUD

Where To Go From Here?

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

Congratulations – you have finished creating a simple Master/Detail app for iOS! You have dived through a crash course of putting an app together, and can dig into further areas of interest.

Here’s what I’d recommend you read next:

  • The iOS Apprentice: iOS Tutorial Team member Matthijs Hollemans has written a detailed iOS tutorial series on iOS development for beginners that covers everything you need to know to get started with iOS, from the ground up. You can get part one for free by signing up for your monthly iOS newsletter, or you can purchase the whole series in the raywenderlich.com store.
  • Beginning Storyboards in iOS 5 Tutorial: To layout your user interface in iOS 5, you will typically use the Storyboard editor, so it is very important to learn how it works.
  • Beginning ARC in iOS 5 Tutorial: It’s also important to understand how memory management is handled in iOS 5 (and how easy it is with ARC!)
  • How To Use Blocks in iOS 5 Tutorial: If you’re new to blocks, you should check this iOS tutorial out. It also gives some good additional practice with Storyboards.
  • Multithreading and Grand Central Dispatch on iOS: It’s also important to learn how to run tasks in the background to keep your app responsive. This iOS tutorial touched on it, but read this for more details.
  • How To Submit Your App to the App Store: And of course when you finish your app, you’ll want to know how to get it onto the App Store! Read this iOS tutorial for a step-by-step guide.
  • And much more! You have tons of tutorials on iOS on this site. Click this link to see your entire list!

I wish you best of luck in your iOS adventures, and I hope you enjoyed making this ScaryBugs app! If you have any questions or comments, please join the form discussion below!

Ray Wenderlich

Ray is an indie software developer currently focusing on iPhone and iPad development, and the administrator of this site. He’s the founder of a small iPhone development studio called Razeware, and is passionate both about making apps and teaching others the techniques to make them.

When Ray’s not programming, he’s probably playing video games, role playing games, or board games.

User Comments

64 Comments

[ 1 , 2 , 3 , 4 , 5 ]
  • I'm curious, the app does not save new data when it is force closed on the device. What needs to be added to save the data or did I miss a step in the tutorial? Looks like even the final sample code you can download does not retain newly added data, only the obvious 4 bugs manually loaded. Thanks in advance!
    bobwilson100
  • Hi, Ray!
    Really Thanks a lot for this tutorial.

    Please help me to solve this in UITableView

    I need to delete multiple rows or cells from UITableView isubsequently...
    Thanks in advance...
    MKR
  • When i try the sample project and close the app i get an error. Open the sample app on iOS simulator and press the home button then double tap the home button and close the app by swiping up.

    Error:

    int main(int argc, char * argv[])
    {
    @autoreleasepool {
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([RWTAppDelegate class]));
    }
    }

    Thread 1 signal SIGKILL

    is this because I am using iOS simulator and shouldn't close the app in the simulator?
    rtorcato
  • If I switch to run the App in Iphone Retina (4-inch) than the app still has the 3.5-inch size and there is a black bar on top and bottom of the app (because its smaller size than the display).
    How can I change this?
    Thanks!
    maxxscho
[ 1 , 2 , 3 , 4 , 5 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in September: iOS 8 App Extensions!

Sign Up - September

RWDevCon Conference?

We are considering having an official raywenderlich.com conference called RWDevCon in DC in early 2015.

The conference would be focused on high quality Swift/iOS 8 technical content, and connecting as a community.

Would this be something you'd be interested in?

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

  • Charlie Fulton
  • Matt Luedke

... 49 total!

Update Team

  • Riccardo D'Antoni

Editorial Team

... 23 total!

Code Team

  • Orta Therox

... 3 total!

Translation Team

  • Myeong Hoon
  • Jiyeon Seo

... 33 total!

Subject Matter Experts

  • Richard Casey

... 4 total!