At this point, you should have a working UITableView. So far, you’ve implemented both the view and the controller
parts of the MVC pattern. Now we’re going to return to Xcode and implement
the model. This needs to be separate from the view and the view
controller, since we want to decouple the way the data is stored from the
way it is displayed as much as possible. This will increase the
reusability of both the classes that handle the UI and the classes that
store the data behind the scenes, allowing us to change how parts of the
application work while affecting as little code as possible.Right-click on the Classes folder in the Groups & Files pane and
select Add→New File. When you see the New
File window shown in Figure 1, make sure Cocoa
Touch Class is selected on the left side of the screen. Next, select
“Objective-C class,” make sure Subclass of NSObject is specified, and click on Next.
You will then be asked for the filename of the new class. Type in
City.m and click on Finish. Xcode will generate a
pair of files, City.h and
City.m, containing the template interface and the
implementation of the new class, and will put them in the Classes folder.
If you look at these files, you can see that since you specified that the
class was a subclass of the base NSObject class, Xcode really hasn’t created a
lot of code. It didn’t know what you wanted the object for, so you’re
going to have to write some code.
Open the City.h file and add variables to hold
the name of our city, a short descriptive paragraph, and an image. Declare
these variables as properties.
#import <Foundation/Foundation.h>
@interface City : NSObject {
NSString *cityName;
NSString *cityDescription;
UIImage *cityPicture;
}
@property (nonatomic, retain) NSString *cityName;
@property (nonatomic, retain) NSString *cityDescription;
@property (nonatomic, retain) UIImage *cityPicture;
@end
I’m declaring the name and description as an NSString, and I’m declaring the variable used to
hold the picture as a UIImage. UIImage is a fairly high-level class that can be
directly displayed in a UIImageView
that we can create inside Interface Builder.
Note:
I could have decided to use an NSMutableString rather
than an NSString. An NSMutableString is a subclass of NSString that manages a mutable
string, which is a string whose contents can be edited.
Conversely, an NSString object
manages an immutable string, which, once created,
cannot be changed and can only be replaced. Using mutable strings here
might give us a bit more flexibility later on, and if you decide you
need it, you can always go back and change these definitions to mutable
strings later. Changing from using an NSString to an NSMutableString is easy since mutable strings
are a subclass and implement all of the methods provided by the NSString class. Going in the opposite
direction is more difficult, unless you have not made use of the
additional functionality offered by the mutable string class.
Open the City.m file and add code to @synthesize the cityName, cityDescription, and cityPicture accessor methods. After doing that,
add a dealloc: method so that the
variables will be released when the class is destroyed. Here’s what your
City.m file should contain:
#import "City.h"
@implementation City
@synthesize cityName;
@synthesize cityDescription;
@synthesize cityPicture;
-(void) dealloc {
[cityName release];
[cityDescription release];
[cityPicture release];
[super dealloc];
}
@end
Because we made use of properties, our accessor methods will be
generated for us automatically. So, we’re done now. Admittedly, this is
just a fairly small class to hold some data, but it illustrates how useful
properties will be for larger, more complex classes.
Let’s go back to the CityGuideDelegate class and prepopulate it with
a few cities. You can put in longer descriptions if you want. If you’re
just using it for personal testing, you could use text and images from
Wikipedia. Later in the book I’ll show you how to retrieve data like this
directly from the network, but for now we’ll hardcode
(embed the data directly into the code; you normally will store your data
outside the app) a few cities into the app delegate class and include the
images inside the application itself rather than retrieving them from the
network. Here’s what the CityGuideDelegate.h file
should look like now (added lines are shown in bold):
#import <UIKit/UIKit.h>
@class RootController;
@interface CityGuideDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
RootController *viewController;
NSMutableArray *cities;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet RootController *viewController;
@property (nonatomic, retain) NSMutableArray *cities;
@end
In the application delegate interface file we declare our City class using the @class declaration, create an NSMutableArray to hold our list of cities, and
declare this mutable array to be a property.
The changes to the application delegate implementation are slightly
more extensive:
#import "CityGuideDelegate.h"
#import "RootController.h"
#import "City.h";
@implementation CityGuideDelegate
@synthesize window;
@synthesize viewController;
@synthesize cities;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
City *london = [[City alloc] init];
london.cityName = @"London";
london.cityDescription =
@"The capital of the United Kingdom and England.";
london.cityPicture = [UIImage imageNamed:@"London.jpg"];
City *sanFrancisco = [[City alloc] init];
sanFrancisco.cityName = @"San Francisco";
sanFrancisco.cityDescription = @"The heart of the San Francisco Bay Area.";
sanFrancisco.cityPicture = [UIImage imageNamed:@"SanFrancisco.jpg"];
City *sydney = [[City alloc] init];
sydney.cityName = @"Sydney";
sydney.cityDescription = @"The largest city in Australia.";
sydney.cityPicture = [UIImage imageNamed:@"Sydney.jpg"];
City *madrid = [[City alloc] init];
madrid.cityName = @"Madrid";
madrid.cityDescription = @"The capital and largest city of Spain. ";
madrid.cityPicture = [UIImage imageNamed:@"Madrid.jpg"];
self.cities = [[NSMutableArray alloc]
initWithObjects:london, sanFrancisco, sydney, madrid, nil];
[london release];
[sanFrancisco release];
[sydney release];
[madrid release];
// Override point for customization after app launch
[window addSubview:viewController.view];
[window makeKeyAndVisible];
}
- (void)dealloc {
[viewController release];
[window release];
[cities release];
[super dealloc];
}
@end
Warning:
If you create a UIImage using the
imageNamed: method as shown in this
example, it is added to the default autorelease pool rather than the
event loop autorelease pool. This means the memory associated with such
images will be released only when the application terminates. If you use
this method with many large images, you’ll find that your application
may quickly run out of memory. Since these images are part of an
autorelease pool, you’ll be unable to free the memory they use when the
device’s operating system calls the didReceiveMemoryWarning: method in the
application delegate when it runs short on memory. You should use the
imageNamed: method sparingly, and
generally only for small images.
1. Adding Images to Your Projects
As you can see, we retrieve the UIImage by name using the imageNamed: class method, but from where are
we retrieving these images? The answer is, from somewhere inside the application
itself. For testing purposes, I sorted through my image collection,
found a representative image for each city (and then scaled and cropped
the images to be the same size [1,000×750 pixels] and aspect ratio using
my favorite image editing software), and copied them into the Xcode
project. To do this yourself, drag and drop each image into the
Resources folder in the Groups & Files pane. This brings up the copy
file drop-down pane, as shown in Figure 2. If you want to
copy the file into the project’s directory rather than create a link to
wherever the file is stored, click on the relevant checkbox. If you do
not copy the files to the project’s directory, they will still be
collected in the application bundle file when Xcode compiles the
application; however, if you later move or delete the file, Xcode will
lose the reference to it and will no longer be able to access it. This
is especially important when copying source code files. In general, I
advocate always checking the box and copying the file into your project,
unless you have a very good reason not to do so.
Note:
There are other ways to add a file to a project. You can also
right-click on the Resources folder and select Add→Existing Files to add a file to the
project.
After you copy the downloaded images into the project, they become
accessible from your code (see Figure 3). It’s generally
advisable not to copy large images into the project. For example, if
your binary gets too large you’ll have distribution problems. Among
other problems, applications above a certain size cannot be downloaded
directly from the App Store on the iPhone unless it is connected to the
Internet via WiFi. Depending on the demographic you’re targeting, this
may limit the market for your application. However, despite this,
bundling images into your application is a good way to get easy access
to small icons and logos that you may want to use in your
project.