Select
You select one or more records
from a SQL database using a select statement. Because a select statement
usually returns multiple rows, you must loop through the row set if you
wish to obtain all records.
while (sqlite3_step(statement) == SQLITE_ROW){
//process row here
}
Obtaining SQLite Column Values
You obtain column values through a method in Listing 16-5. Using these methods will become more apparent after the next task.
Listing 5. Methods for obtaining column data (from SQLite online documentation)
const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); int sqlite3_column_bytes(sqlite3_stmt*, int iCol); int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); double sqlite3_column_double(sqlite3_stmt*, int iCol); int sqlite3_column_int(sqlite3_stmt*, int iCol); sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); int sqlite3_column_type(sqlite3_stmt*, int iCol); sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);
|
Note
The int iCol arguments in the methods in Listing 16-5
are a zero-based index into the columns in the results of the
sqlite3_stmt, not an index into the columns of a SQLite database table.
Return to your MyDBProject in Xcode. In Classes, create a new group called Model. Create
a new Objective-C class in the Model group called PhotosDAO. Create
another Objective-C class in the same group called PhotoDAO. Add a name, photoID, and photo property to PhotoDAO.h and PhotoDAO.m (Listings 6 and 7). Listing 6. PhotoDAO.h
#import <Foundation/Foundation.h> @interface PhotoDAO : NSObject { NSString * name; NSInteger photoID; UIImage * photo; } @property (nonatomic, retain) NSString * name; @property (nonatomic, assign) NSInteger photoID; @property (nonatomic, retain) UIImage * photo; @end
|
Listing 7. PhotoDAO.m
#import "PhotoDAO.h" @implementation PhotoDAO @synthesize name; @synthesize photoID; @synthesize photo; - (void) dealloc { [name release]; [photo release]; [super dealloc]; } @end
|
Open PhotosDAO.h and import SQLite3. Add a reference to the database you will use (Listing 8). Listing 8. PhotosDAO.h
#import <Foundation/Foundation.h> #import <sqlite3.h> @interface PhotosDAO : NSObject { sqlite3 *database; } - (NSMutableArray *) getAllPhotos; @end
|
Add a getAllPhotos method to PhotosDAO and implement the method (Listing 9). Listing 9. PhotosDAO.m
#import "PhotosDAO.h" #import "PhotoDAO.h" @implementation PhotosDAO - (NSMutableArray *) getAllPhotos { NSMutableArray * photosArray = [[NSMutableArray alloc] init]; @try { NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *theDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"myDatabase.sqlite"]; BOOL success = [fileManager fileExistsAtPath:theDBPath]; if (!success) { NSLog(@"Failed to find database file '%@'.", theDBPath); } if (!(sqlite3_open([theDBPath UTF8String], &database) == SQLITE_OK)) { NSLog(@"An error opening database, normally handle error here."); } const char *sql = "SELECT id,name,photo FROM photos"; sqlite3_stmt *statement; if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) != SQLITE_OK){ NSLog(@"Error, failed to prepare statement, handle error here."); } while (sqlite3_step(statement) == SQLITE_ROW) { PhotoDAO * aPhoto = [[PhotoDAO alloc] init]; aPhoto.photoID = sqlite3_column_int(statement, 0); aPhoto.name = [NSString stringWithUTF8String:(char *) sqlite3_column_text(statement, 1)]; const char * rawData = sqlite3_column_blob(statement, 2); int rawDataLength = sqlite3_column_bytes(statement, 2); NSData *data = [NSData dataWithBytes:rawData length: rawDataLength]; aPhoto.photo = [[UIImage alloc] initWithData:data]; [photosArray addObject:aPhoto]; [aPhoto release]; } if(sqlite3_finalize(statement) != SQLITE_OK){ NSLog(@"Failed to finalize data statement, error handling here."); } if (sqlite3_close(database) != SQLITE_OK) { NSLog(@"Failed to close database, normally error handling here."); } } @catch (NSException *e) { NSLog(@"An exception occurred: %@", [e reason]); return nil; } return photosArray; } @end
|
Open
MyDBProjectViewController.h and add an NSMutableArray property to hold
the photos. Add an IBOutlet for a UIImageView. Add a UILabel named
theLabel, add an IBAction, and name the method changeImage (Listing 10). Listing 10. MyDBProjectViewController.h
#import <UIKit/UIKit.h> @interface MyDBProjectViewController : UIViewController { NSMutableArray * photos; UIImageView * theImageView; UILabel * theLabel; } @property (nonatomic, retain) NSMutableArray * photos; @property (nonatomic, retain) IBOutlet UIImageView * theImageView; @property (nonatomic, retain) IBOutlet UILabel * theLabel; - (IBAction) changeImage: (id) sender; @end
|
Open MyDBProjectViewController.m and synthesize photos and theImageView (Listing 11). Listing 11. MyDBProjectViewController.m
#import "MyDBProjectViewController.h" #import "PhotoDAO.h"; #import "PhotosDAO.h"; @implementation MyDBProjectViewController @synthesize photos; @synthesize theImageView; @synthesize theLabel; - (void)viewDidLoad { PhotosDAO * myPhotos = [[PhotosDAO alloc] init]; self.photos = [myPhotos getAllPhotos]; [self.theImageView setImage:((PhotoDAO *)[self.photos objectAtIndex:0]).photo]; [self.theLabel setText:((PhotoDAO *) [self.photos objectAtIndex:0]).name]; [myPhotos release]; [super viewDidLoad]; } - (IBAction) changeImage: (id) sender { static NSInteger currentElement = 0; if(++currentElement == [self.photos count]) currentElement = 0; PhotoDAO * aPhoto = (PhotoDAO *) [self.photos objectAtIndex: currentElement]; [self.theLabel setText:aPhoto.name]; [self.theImageView setImage:aPhoto.photo]; } - (void)dealloc { [photos release]; [theImageView release]; [theLabel release]; [super dealloc]; } @end
|
Implement the viewDidLoad and changeImage methods so that they match Listing 11. Save your changes and open MyDBProjectViewController.xib. Add a toolbar, a label, and a UIImageView (Figure 1). Change the button’s title to Next. Remove the text from the label.
Connect
the File’s Owner theLabel outlet to the label added to the toolbar.
Connect the theImageView outlet to the UIImageView. Connect the
changeImage action to the Next button. Save your changes. Run the application in iPhone Simulator, as shown in Figures 2 and 3.
|
Note
You
would normally never load an entire database at once in a real
application, especially when using large blobs, like this example.
Memory is limited in an iOS device—only load what you need when you need
it.
The Model-View-Controller
When writing a program for
any platform, you should adhere to the MVC design pattern as closely as
possible. Rather than placing the database logic in a view or view
controller, you created separate classes, insulating the view and
controller layers from the database layer. The MyDBProjectViewController
knows nothing about the underlying SQLite3 library; the view controller
only knows about PhotosDAO and PhotoDAO. Notice you further separated
the code by placing it in its own group, Model, under Classes. All this
separation makes debugging and maintaining the program easier.
Opening the Database
To
keep the task’s length manageable and focused, rather than creating
several data access methods in PhotosDAO, you only created one.
- (NSMutableArray *) getAllPhotos;
This method returns an array
of PhotoDAO objects. The getAllPhotos method first finds the database
and opens it. Because the database is in the resources folder, you can
access it directly using the bundle’s resourcePath. (When you want to
create an application that uses canned [predefined] data, this task
illustrated how to create that data in advance [using SQLite Manager in
Firefox] and then embed it in your application.)
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *theDBPath = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent: @"myDatabase.sqlite"];
After obtaining the database’s path, you open it.
if (!(sqlite3_open([theDBPath UTF8String], &database) == SQLITE_OK))
Notice that you obtain the
UTF8String from the NSString before passing the sqlite3_open method the
path. Since opening the database is a common activity, you might want to
move that portion of the code into its own method for easy reuse.
Querying the Data
After opening the
database, you query it for the photo records. If you have ever worked
with a database using code, for instance, Java Database Connectivity
(JDBC), then this code should look familiar. The getAllPhotos method
first creates the SQL select string. Next, the method places the string
in a statement and then queries the database. After obtaining the data,
getAllPhotos loops through each record.
For each new record,
getAllPhotos creates a new PhotoDAO. The newly created PhotoDAO object’s
values are then set to the appropriate values from the current record.
After initializing the PhotoDAO object, getAllPhotos places the object
into PhotosDAO’s photosArray.
Loading a Blob into NSData
This code snippet is useful.
It shows you a quick, easy way to load a blob, any blob, into an NSData
object. First, load the blob into a C string.
const char * rawData = sqlite3_column_blob(statement, 2);
Second, obtain the blob’s byte size.
int rawDataLength = sqlite3_column_bytes(statement, 2);
Third, create an NSData class using the C string and size variables.
NSData *data = [NSData dataWithBytes:rawData length:rawDataLength];
As
you already know the database blob is an image, you initialize the
PhotoDAO’s photo property using the UIImage’s initWithData method.
aPhoto.photo = [[UIImage alloc] initWithData:data];
This same technique works for other binary data as well (replacing UIImage with the appropriate class).
Closing the Database
When finished using a statement, you release its resources by finalizing the statement.
if(sqlite3_finalize(statement) != SQLITE_OK)
After you no longer need the database, you close it.
if (sqlite3_close(database) != SQLITE_OK)
Selecting all records only
has limited value. Rarely will you use SQL statements where you do not
wish to limit the results returned. For this, you typically add
parameters to your SQL statements and then replace the parameters with
values in your program. This is called binding your program’s values to
the statements’ parameters. Programs usually also allow more than simply
selecting data; most applications allow users to add, edit, and delete
records.