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

An iOS tutorial for complete beginners that shows you how to make your first iPhone app, from scratch! By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

A Scary Data Model: Implementation

Ok so Control-click on the Model group and click New File…. Select the iOS\Cocoa Touch\Objective-C class template, and click Next.

Creating a file with the Objective-C class template

Name the class RWTScaryBugData, enter NSObject for subclass, and click Next.

Creating a class that derives from NSObject in Xcode

In the final popup, click Create again. If all went well, your Project Navigator should now look similar to this:

Project organization, part 3

Ok, time to create your RWTScaryBugData class. Replace RWTScaryBugData.h with the following:

#import <Foundation/Foundation.h>

@interface RWTScaryBugData : NSObject

@property (strong) NSString *title;
@property (assign) float rating;

- (id)initWithTitle:(NSString*)title rating:(float)rating;

@end

This is pretty simple stuff – you’re just declaring an object with two properties – a string for the name of the bug, and a float for how scary you rated it. You use two property attributes for these:

  • strong: This specifies that the runtime should automatically keep a strong reference to the object. This is a fancy way of saying that the ARC runtime will keep the object in memory as long as there’s a reference to it around, and deallocate it when no references remain. For more information, check out your Beginning ARC in iOS 5 Tutorial.
  • assign: This means the property is set directly, with no memory management involved. This is what you usually set for primitive (non-object) types like a float.

You also define an initializer for the class, so you can set the title and rating when you create the bug.

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

#import "RWTScaryBugData.h"

@implementation RWTScaryBugData

@synthesize title = _title;
@synthesize rating = _rating;

- (id)initWithTitle:(NSString*)title rating:(float)rating {
  if ((self = [super init])) {
    self.title = title;
    self.rating = rating;
  }
  return self;
}

@end

Again, extremely simple stuff here. You synthesize your properties, and create your initializer to fill in your instance variables from the passed-in parameters. Note there is no need for dealloc, since you are using ARC.

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

Replace RWTScaryBugDoc.h with the following:

#import <Foundation/Foundation.h>

@class RWTScaryBugData;

@interface RWTScaryBugDoc : NSObject

@property (strong) RWTScaryBugData *data;
@property (strong) UIImage *thumbImage;
@property (strong) 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 RWTScaryBugDoc.m with the following:

#import "RWTScaryBugDoc.h"
#import "RWTScaryBugData.h"

@implementation RWTScaryBugDoc
@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])) {
    self.data = [[RWTScaryBugData alloc] initWithTitle:title rating:rating];
    self.thumbImage = thumbImage;
    self.fullImage = fullImage;
  }
  return self;
}

@end

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

A Different Kind of Bug List

First, you’ll set up your table view so it can handle displaying a list of RWTScaryBugDocs. The first thing you have to do is modify your table view so that it returns a dynamic list of rows (rather than a hardcoded single row that the template set up for you).

To do this, open Main.storyboard. This allows you to visually layout the different “screens” in your app. As you can see, the app is currently set up to have a navigation controller (the thing that makes it easy to slide between different screens), with the root controller the master screen, and a secondary controller as the detail screen.

Select the Master View Controller, and in the selection area in the left panel, select the Table View. In the inspector to the right, make sure the Content is set to Dynamic Prototypes.

Setting table view to use dynamic prototype cells

This is what allows you to design a single table view cell the way you like in the Storyboard Editor, and easily create instances of the cell via code. You just want a basic cell, so make sure the cell is using the Basic style.

Select the Table View Cell on the left, and in the Attributes Inspector make sure the Style is set to Basic. Also set the Identifier to MyBasicCell.

Setting cell style and reuse identifier

For more information on creating custom cells, check out your Beginning Storyboards in iOS 5 Tutorial.

OK, now that you have your table view set up correctly visually, you just need to update the code to fill in the table with a list of scary bugs.

You’ll store your RWTScaryBugDocs in a NSMutableArray, the collection class that you use for arrays that should be able to dynamically change in size.

Add the following line to RWTMasterViewController.h, between the @interface and @end lines:

@property (strong) NSMutableArray *bugs;

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

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

// At top of file
#import "RWTScaryBugDoc.h"
#import "RWTScaryBugData.h"

// After @implementation
@synthesize bugs = _bugs;

// At the end of viewDidLoad
self.title = @"Scary Bugs";

// Replace the return statement in tableView:numberOfRowsInSection with the following:
return _bugs.count;

// Replace tableView:cellForRowAtIndexPath with the following
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  UITableViewCell *cell = [tableView
               dequeueReusableCellWithIdentifier:@"MyBasicCell"];
  RWTScaryBugDoc *bug = [self.bugs objectAtIndex:indexPath.row];
  cell.textLabel.text = bug.data.title;
  cell.imageView.image = bug.thumbImage;
  return cell;
}

Ok, finally something interesting to discuss!

First, note that you set a property 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, you should see “Scary Bugs” up top!

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

Finally, you 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.

Take a look to this method in detail, since this is particularly important:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  UITableViewCell *cell = [tableView
               dequeueReusableCellWithIdentifier:@"MyBasicCell"];
  RWTScaryBugDoc *bug = [self.bugs objectAtIndex:indexPath.row];
  cell.textLabel.text = bug.data.title;
  cell.imageView.image = bug.thumbImage;
  return cell;
}

The first line calls 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, you just create a new cell based on the cell you set up in Interface Builder (remember how you set it as basic, and named it MyBasicCell).

In the Storyboard Editor you can customize the layout of the cell, or use one of the built-in ones. In your case, you chose the Basic style, which adds a label and image you can set.

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, you configure the cell by setting its textLabel and imageView (which are available with the Basic style).

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