iPhone SDK Articles

Saturday, October 25, 2008

SQLite Tutorial - Selecting Data


Like any other applications you may have the need to store data in some kind of a database. With iPhone applications we can use SQLite for FREE. In the first part of this tutorial, I will show you how to do some basic operations using SQLite database.

Introduction

Like any other applications you may have the need to store data in some kind of a database. For iPhone applications, we can use SQLite for free. SQLite is a software library that implements a self-contained, serverless, zero-configuration, transactional SQL database engine. Note: iPhone applications cannot work with remote databases.

In this tutorial, I will discuss how to create a new SQLite database and use it in a iPhone application.

This is how the application will look like
SQLite Manager
To use SQLite in your application, you do not have to install any new software on your mac. You can either use the command line to create a new SQL database or use SQLite Manager for Firefox add-on, which is what I use.

The screenshots below shows you the database schema used, which is very simple.

iPhone App
Create a new project by selecting Navigation-based application. The name of the project used for this tutorial is "SQL". We first need to add a library which understands how to communicate with the SQLite database. In Xcode select "Frameworks" folder and click on the Action dropdown and select Add -> Existing Frameworks and browse to /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.1.sdk/usr/lib and select libsqlite3.0.dylib file and it will be added in the "Frameworks" folder.

Now add the SQLite database file to the "Resources" folder in Xcode.

Next we need to create a data structure to hold the data from the database. Create a new class and name it "Coffee". This is how the class looks like.

#import <UIKit/UIKit.h>
#import <sqlite3.h>

@interface Coffee : NSObject {

NSInteger coffeeID;
NSString *coffeeName;
NSDecimalNumber *price;

//Intrnal variables to keep track of the state of the object.
BOOL isDirty;
BOOL isDetailViewHydrated;
}

@property (nonatomic, readonly) NSInteger coffeeID;
@property (nonatomic, copy) NSString *coffeeName;
@property (nonatomic, copy) NSDecimalNumber *price;

@property (nonatomic, readwrite) BOOL isDirty;
@property (nonatomic, readwrite) BOOL isDetailViewHydrated;

@end


Note that in the "Coffee" class the price variable is declared as "NSDecimalNumber" because in the database its corresponding column is of type "REAL". Note: Although SQLite does not enforce data type constraints, it is good practice to enforce data type constraints at the application level. The two boolean variables keep track of the state of the object. The boolean "isDirty" tells us if the object was changed in memory or not and "isDetailViewHydrated" tell us, if the data which shows up on the detail view is fetched from the database or not.

NOTE: It is good practice to fetch only the data required to show on the screen, which makes the application load faster. So in our case, we will only get the primary key and the name of the coffee from the database as the price is only shown in the detail view.

Synthesize the properties and release price and coffeeName in the dealloc method. This is how the implementation file of class "Coffee" looks like

#import "Coffee.h"

@implementation Coffee

@synthesize coffeeID, coffeeName, price, isDirty, isDetailViewHydrated;

- (void) dealloc {

[price release];
[coffeeName release];
[super dealloc];
}

@end

Copying the database
The first thing we need to do when the application loads is to check whether the user's phone has the database or not, if not then we copy it to the user's phone. We will create two methods which will help us in copying the database to the user's phone. This is how SQLAppDelegate header file will look like

#import <UIKit/UIKit.h>

@class Coffee;

@interface SQLAppDelegate : NSObject <UIApplicationDelegate> {

UIWindow *window;
UINavigationController *navigationController;

//To hold a list of Coffee objects
NSMutableArray *coffeeArray;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;

@property (nonatomic, retain) NSMutableArray *coffeeArray;

- (void) copyDatabaseIfNeeded;
- (NSString *) getDBPath;

@end

coffeeArray is the array used to hold all the "Coffee" objects. It is declared in the application delegate file and not anywhere else is because the object lives in memory as long as the application is running and it also gets a notification when the application is being terminated. The method copyDatabaseIfNeeded is used to copy the database on the user's phone when the application is finished launching. Another method used with "copyDatabaseIfNeeded" is "getDBPath" which gets the database location on the user's phone.

This is how "copyDatabaseIfNeeded" method looks like

- (void) copyDatabaseIfNeeded {

//Using NSFileManager we can perform many file system operations.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSString *dbPath = [self getDBPath];
BOOL success = [fileManager fileExistsAtPath:dbPath];

if(!success) {

NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"SQL.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:dbPath error:&error];

if (!success)
NSAssert1(0, @"Failed to create writable database file with message '%@'.", [error localizedDescription]);
}
}

and this is how the getDBPath method looks like

- (NSString *) getDBPath {

//Search for standard documents using NSSearchPathForDirectoriesInDomains
//First Param = Searching the documents directory
//Second Param = Searching the Users directory and not the System
//Expand any tildes and identify home directories.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
return [documentsDir stringByAppendingPathComponent:@"SQL.sqlite"];
}

In "copyDatabaseIfNeeded" method we get the NSFileManager object with which we can perform some basic file management tasks. The method "getDBPath" gives us the database location on the user's phone. Method "NSSearchPathForDirectoriesInDomains" is used to find documents in the user's documents directory expanding the tildes so we get the whole path. Using the "fileManager" object we check if the database exists or not, if it doesn't exists then we copy it to the user's phone from the application bundle. Method "copyDatabaseIfNeeded" is called from "applicationDidFinishLaunching". Once the database is copied to the user's phone, we need to display a list of coffee's from the database on the UITableView.

Getting data from the database
The approach that I have taken here to get the data from the database is a little clean, as I have all my database operations in the Coffee Class. I have declared a class method in the "Coffee" class which is responsible to get the data from the database and fill the coffeeArray which is declared in the application delegate object (SQLAppDelegate).

This is how the header file of the "Coffee" class will look like after the method decelerations are added.

#import <UIKit/UIKit.h>
#import <sqlite3.h>

@interface Coffee : NSObject {

NSInteger coffeeID;
NSString *coffeeName;
NSDecimalNumber *price;

//Intrnal variables to keep track of the state of the object.
BOOL isDirty;
BOOL isDetailViewHydrated;
}

@property (nonatomic, readonly) NSInteger coffeeID;
@property (nonatomic, copy) NSString *coffeeName;
@property (nonatomic, copy) NSDecimalNumber *price;

@property (nonatomic, readwrite) BOOL isDetailViewHydrated;

//Static methods.
+ (void) getInitialDataToDisplay:(NSString *)dbPath;
+ (void) finalizeStatements;

//Instance methods.
- (id) initWithPrimaryKey:(NSInteger)pk;

@end

The method "getInitialDataToDisplay" gets the data from the database and creates "Coffee" objects using "initWithPrimaryKey" method and fills the objects in the coffeeArray which is declared in SQLAppDelegate.

This is how the "getInitialDataToDisplay" method looks like

+ (void) getInitialDataToDisplay:(NSString *)dbPath {

SQLAppDelegate *appDelegate = (SQLAppDelegate *)[[UIApplication sharedApplication] delegate];

if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK) {

const char *sql = "select coffeeID, coffeeName from coffee";
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL) == SQLITE_OK) {

while(sqlite3_step(selectstmt) == SQLITE_ROW) {

NSInteger primaryKey = sqlite3_column_int(selectstmt, 0);
Coffee *coffeeObj = [[Coffee alloc] initWithPrimaryKey:primaryKey];
coffeeObj.coffeeName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 1)];

coffeeObj.isDirty = NO;

[appDelegate.coffeeArray addObject:coffeeObj];
[coffeeObj release];
}
}
}
else
sqlite3_close(database); //Even though the open call failed, close the database connection to release all the memory.
}

initWithPrimaryKey method is very simple and it looks like this

- (id) initWithPrimaryKey:(NSInteger) pk {

[super init];
coffeeID = pk;

isDetailViewHydrated = NO;

return self;
}

In "getInitialDataToDisplay" method we first get a reference to the application delegate because that is where the coffeeArray is declared. We open the database using sqlite3_open method which takes the database path and a database object. The database object is declared as static in the "Coffee.m" file.

#import "Coffee.h"

static sqlite3 *database = nil;

@implementation Coffee
...

After we open the connection to the database we create a select statement and if that returns "SQLITE_OK" we execute the select statement using sqlite3_step method, which will return "SQLITE_ROW" if the operation is a success and it has one or more rows to return. To get a full list of return codes click here. We then get the coffeeID, coffeename and create "Coffee" objects using "initWithPrimaryKey" method. When reading data from the database, the column index starts from zero instead of one. The coffee object is added to the array and we do this for n number of rows. If the connection to the database fails, only then we close the connection and release any resources associated with the connection object. The static database connection is left open to be used by the "Coffee" class.

We close the database connection in the "finalizeStatements" method which is called from "applicationWillTerminate" which is in SQLAppDelegate.m file.

+ (void) finalizeStatements {

if(database) sqlite3_close(database);
}

Now all we have to do is call the right methods from "applicationDidFinishLaunching" method in SQLAppDelegate.m file. This is how the code looks like

- (void)applicationDidFinishLaunching:(UIApplication *)application {

//Copy database to the user's phone if needed.
[self copyDatabaseIfNeeded];

//Initialize the coffee array.
NSMutableArray *tempArray = [[NSMutableArray alloc] init];
self.coffeeArray = tempArray;
[tempArray release];

//Once the db is copied, get the initial data to display on the screen.
[Coffee getInitialDataToDisplay:[self getDBPath]];

// Configure and show the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}

To recap, we first copy the database to the user's phone if it does not exists, then we initialize the coffee array and get the initial data to display on the UITableView.

Display data in the UITableView
Since the Coffee Array is in the application delegate file (SQLAppDelegate), for simplicity I' am going to add the "SQLAppDelegate.h" header file in "SQL_Prefix.pch" file so that the file is added to all the files in the project. This is how the file should look like

#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "SQLAppDelegate.h"
#endif

Open "RootViewController.h" file and create a variable of type SQLAppDelegate like this

#import <UIKit/UIKit.h>

@class Coffee;

@interface RootViewController : UITableViewController {

SQLAppDelegate *appDelegate;
}

@end

We are able to do the above because "SQLAppDelegate.h" file is added as a header file to all the files in the project.

Open "RootViewController.m" and import "Coffee.h" file, change the "viewDidLoad" method like this

- (void)viewDidLoad {
[super viewDidLoad];

self.navigationItem.rightBarButtonItem = self.editButtonItem;

appDelegate = (SQLAppDelegate *)[[UIApplication sharedApplication] delegate];

self.title = @"Coffee List";
}

We get a reference to the application delegate in the second line and everything is self explanatory. Using the appDelegate we can access the array.

Now all we have to do is tell the table view how many rows to expect and set the text property of the UITableViewCell which is returned in "cellForRowAtIndexPath" method.

Method "numberOfRowsInSection" returns the number of rows UITableView should expect in a section. By default "numberOfSectionsInTableView" returns 1, so the UITableView looks like a regular table.

Below is a code snippet of "numberOfRowsInSection"

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [appDelegate.coffeeArray count];
}

and cellForRowAtIndexPath

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}

//Get the object from the array.
Coffee *coffeeObj = [appDelegate.coffeeArray objectAtIndex:indexPath.row];

//Set the coffename.
cell.text = coffeeObj.coffeeName;

// Set up the cell
return cell;
}

cellForRowAtIndexPat is called n number of times, where n is the total number of objects in the coffeeArray which is returned in "numberOfRowsInSection" method. We get the Coffee object from the array using "objectAtIndex" method and set the coffeeName as the text of the UITableViewCell.

Conclusion
This concludes part one of the SQLite Tuturial, where we learn how easy it is to manage databases using SQLite Manager and using it in your iPhone applications. Overall SQLite is FREE for all to use. You also learn how to copy database to the user's phone and perform select operations on the database.

The next tutorial will show you how to delete data.

Happy Programming,
iPhone SDK Articles



Attachments

Suggested Readings


44 comments:

melfar said...

Hi! Thanks for the post.
I have been reading you for quite some time.

I have a couple of questions:
1. Not being much of a fan of C APIs, I was looking for a free opensource ObjC wrapper library around sqlite, and only found this one useful: http://www.gusmueller.com/blog/archives/2005/3/22.html
What concerns me is that the library seems to be unsupported and I'm not sure it's either bug free or memory leak free (it's certainly NOT a complete sqlite API).
Do you have any recommendations on such kind of a wrapper library?

2. Why do we need to copy the database? Why can't we just open it from the original location? We only have user on iphone.

3. What's the 'nonatomic' property attribute is all about? Can't find any easily comprehensive information on that one.

Thanks for the tutorials, they gave me the necessary critical mass to begin iphone development.

iPhone SDK Articles said...

Hi melfar,

1. You can use entropydb. http://code.google.com/p/entropydb/

2. This is a good question, I thought the database would not be writable but it is (partially). However, the database changes were not retained once i quit the simulator and started the app again all my changes were gone.

3. nonatomic - A property is specified as nonatomic if used in a single-threaded environment. Properties are atomic by default, so the getter/setter methods provide access to properties in a multi-threaded environment.

Happy Programming,
iPhone SDK Articles

Alladinian said...

Hi there!

Thanks a lot for your tutorials!

I have a question (may be silly but I am just learning programming :P)

I understand the way that we are going to add data to our database, but what if we would like to include images as well? (For example we may iclude also a graphic for a selected item)

Thank you again in advance :)

iPhone SDK Articles said...

@Alladinian,

Very good question, I will write up an article on how to save images in the database.

You can follow my twitter feed to keep in touch with this site.

Happy Programming,
iPhone SDK Articles

nutsmuggler said...

Hello, and thanks for the tut, it's very easy to follow and doesn't take anything for granted.

Now, unless I misread something there are a couple of problems.
- The extension of the sqlite database is .db, at least that's the default extension provided by the sqlite3 installed on Leopard. I had to change the extension in the code to have the app working.
- There's a problem with the CoffeeName property, at least in iPhone SDK 2.1. In the UITableView instead of
Latte
I get
'Latte'
with the apostrophe signs. I am sure that the value in the DB is correct, and also that the value is correcttly inserted in the table (I tried to insert a custom NSString and it worked). So I guess this is a conversion problem. Any idea?

Cheers,
Davide

stenson said...

When you said "iPhone applications cannot work with remote databases" does this mean that I can't use a database that is hosted on the web for use in my iPhone app? Like for example: if I had a community website with user profiles,etc. and I wanted to make an app to extend the website, can I use the same database?

Anonymous said...

Hi! Thanks for the tutorial.

I am using the 2.2 SDK and I can't seem to get the data to be displayed in the TableView. I downloaded your source code and made sure there was an entry in the database, however nothing loads as shown in the first picture above. Any ideas why? Thanks!!

Anonymous said...

Hi. I already read the Erica Sadun's book. And as I'm looking to create a little Sqlite app, I decided to follow this tutorial to learn how to manage this databases better.

Because of that, I have a little comment. I followed this steps but the Execution failed. Then I compared your sources (thanks for sharing) and found the error. It's necessary to add to the SQLAppDelegate.m the import of Coffee.h, and the synthesize of coffeeArray in the Implementation. So the code of SQLAppDelegate.m was:

#import "RootViewController.h"
#import "Coffee.h"

@implementation SQLAppDelegate

@synthesize window;
@synthesize navigationController;
@synthesize coffeeArray;

- (void) copyDatabaseIfNeeded {

Anyway, thanks a lot for this tutorial. I'm moving to the 2nd part now.
Just one question. What does synthesize means, and why is it necessary??? I'm a C++ programmer, and that instruction simply escapes from me. Thanks.

Daniel said...

Stumbled on your site. Helpful tutorials. After completing this tutorial, I ran the simulator and no data was displayed. I thought something might be wrong with my database. I downloaded your source and noticed that when you run the simulator on your source, nothing is displayed either.

Do you need to run this off a real iPhone to test it? Not sure what's going on.

Thanks in advance,

Daniel

iPhone SDK Articles said...

I think array was synthesized and the code on the site does reflect that, but I may have missed it and updated it after you looked at it.

Thanks for the heads up.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@nutsmuggler I ran the code using 2.1 and it seem to ran without and I did not see any single quotes around the text. I also did not have to change the db extension, can you tell me what is the version of your OS?

Thanks.

Glad you enjoyed the article.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@stenson Yes, you cannot use any remote databases, though if you can consume an XML feed generated by your database.

Hope it helps.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@Daniel looks like you are not the only one with this problem, I will let you know soon. This is weird for sure.

Thanks for bringing it to my attention

Happy Programming,
iPhone SDK Articles

nutsmuggler said...

Hi, nevermind my previous observation, I eventually realised it was a bug of the app I was using to manage sqlite data (Base 1.0), solved in the next release.
Cheers,
Davide

trsills said...

In regards to Daniel's issue...I suspect the issue is that data just wasn't entered into the DB's tables since this isn't covered but probably assumed in the steps leading to the DB creation.

Also, if data was subsequently added and the DB was then recopied back into the Resource folder, there's still an issue of the old DB existing in the iphone simulator's file structure. To which I found that you need to delete the respective folder created under library->applicationsupport->iphonesimulator

Anonymous said...

Hai,

tutorial is nice... When i implemented my own application then it shows the error as:

Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSCFArray objectAtIndex:]: index (1) beyond bounds (0)'

Could u give some ideas/suggestion about this error...

mooders said...

Hi there,

Very good tutorials on this site - thanks so much for your efforts in creating them!

With this SQLite tutorial, I consistently get the following exception:
Application Specific Information:
iPhone Simulator 2.2 (77.4.9), iPhone OS 2.0 (5A345)
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to create writable database file with message 'Operation could not be completed. No such file or directory'.'

Any ideas please?

Many thanks,

Neil

iPhone SDK Articles said...

@mooders Can you confirm the name of the file is correct? Looks like it cannot find the file. The database file has to be under resources.

Let me know if this helpes or not.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@Anonymous Looks like the index is greater then the items in the array. This mean the index is not within the bounds of the array. Where does this happen?

Happy Programming,
iPhone SDK Articles

devaski said...

SQLiteBooks.- With two tables.

I am having two tables in the SQl table table1 & table2.

I am relating table1.pk with table2.fk

In MasterViewController, > table.name , selectRowAtIndexPath method, I want to display the values from table2 into detailViewController, where table2.foreign key matches table1.pk?


I have created two separate database instruction class files namely table1.h & table2.h also created two different hydrate & dehydrate statements



MaserViewController.m

- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyIdentifier"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"MyIdentifier"] autorelease];
}
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

Tableone *tableone = (Tableone *)[appDelegate.tableones objectAtIndex:indexPath.row];
cell.text = tableone.name;
return cell;
}



- (NSIndexPath *)tableView:(UITableView *)tv willSelectRowAtIndexPath:(NSIndexPath *)indexPath {

AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
Tabletwo *tabletwo = [appDelegate.tabletwos objectAtIndex:indexPath.row];
DetailViewController *controller = self.detailViewController;
[tabletwo hydrate];

controller.tabletwo = tabletwo;

[self.navigationController pushViewController:controller animated:YES];
[controller setEditing:NO animated:NO];
return nil;
}


In appdeledate.m
- (void)initializeDatabase {
NSMutableArray *tableoneArray = [[NSMutableArray alloc] init];
self.tableones = tableoneArray;
[tableoneArray release];

// The database is stored in the application bundle.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

NSString *path = [documentsDirectory stringByAppendingPathComponent:@"db.sql"];
if (sqlite3_open([path UTF8String], &database) == SQLITE_OK) {

const char *sql = "SELECT pk FROM tableone";

sqlite3_stmt *statement;

if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
int primaryKey = sqlite3_column_int(statement, 0);

Tableone *tableone = [[Tableone alloc] initWithPrimaryKey:primaryKey database:database];
[tableones addObject:tableone];
[tableone release];


}
}
sqlite3_finalize(statement);
} else {
sqlite3_close(database);
NSAssert1(0, @"Failed to open database with message '%s'.", sqlite3_errmsg(database));
}
}


Any suggestions how to get values from tabletwo where tabletwo.fk is equal to tableone.pk ?

mooders said...

You were right of course - I needed to change the name of the database file. D'Oh! Thanks for the response.

tlampo said...

Hello,

Thank you for posting this. I hope this blog can get me started on programming a few apps for the iPhone.

I followed every instruction you posted here carefully, and I'm getting a weird error. Hope you can help me...

When I'm ready to compile the App, I get an error after the
#import "RootViewController.h" action in RootViewController.m and SQLAppDelegate.m

The error says "error: syntax error before 'SQLAppDelegate'"

From there on, there is an error for every call I make to the appDelegate variable you created in RootViewController.h

I have checked and rechecked RootViewController.h and SQL_Prefix.pch and everything seems to be fine (they match the code on this post)

Do you, by any chance, know what is happening to me ? I'm going crazy with this error :P

Thanks again for sharing your knowledge. Hope you can give me a hand...

Cheers,

tlampo

tlampo said...

UPDATE:

I realised the problem isn't when I create the variable appDelegate, but instead in the SQLAppDelegate.h class.

For some reason, even if Xcode reports no errors on my SQLAppDelegate.h class, it does not seem to recognise the interface declaration.

I will look deeper into this problem and try to find an answer.

Thanks again for your help. Sorry for the inconvenience...

Cheers,

tlampo

tlampo said...

UPDATE #2:

Feeling stupid, now... I misspelt SQL as SLQ in my code. That created 17 errors for me...

Please ignore my other questions. Thank you for keeping this blog. I'm really sorry again for inmediatly asking for help instead of reading my code carefully...

Cheers,

tlampo

iPhone SDK Articles said...

@tlampo No problem, let meknow if I can be of any help.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@devaski It depends how you have design everything. This is how I would do it.

In willSelectRowAtIndexPath method, I will find out the primary key for table 1. I will check if table2 is hydrated or not with the table 1 primary key value. If not I will get it from the database.

In your code (in willSelectRowAtIndexPath method) you get the table2 object from teh array by index and not by primary key. Here you should first get the table1 object, get it's primary key and then check if table2 is hydrated or not.

This can easily be done with a simple inner join query on the two tables.

I hope this helps.

Happy Programming,
iPhone SDK Articles

Anonymous said...

So, got everything working, and this is going to sound like a STUPID question, but I am new to all of this, so the Q is: Where is Latte coming from? How is it being displayed? Did not insert it into the DB at any point. Also, when I insert other things directly into the DB, they're not showing up in the build and run?

Thanks for these tutorials, they are great!!

iPhone SDK Articles said...

@Anonymous
"Latte" was entered in the db when it was created. When the application gets installed on the iPhone the app and the database is copied here ~/Library/Application Support/iPhone Simulator/User/Applications/{App Guid ID}; did you add the data to the database located at this location?

No question is a stupid question, only a stupid answer. So I hope my answer wasn't stupid.

I hope this helps.

Happy Programming,
iPhone SDK Articles

OgreSwamp said...

Hi.
Many thanks for your tutorial.

I'm a newbie in Objective-C programming and I have a question.

Method (or "message") getInitialDataToDisplay looks for me as part of "controller" in "model" class Coffee. Usage of appDelegate looks strange for me. Is it possible to pass link to coffeeArray as second argument of getInitialDataToDisplay? Do U think that it is possible to make your code more MVC-like?

ravi said...

Thanks a lot for sucha nice tutorial , learned so much from here !!

now i have some confusion regarding ,

"create a new folder in iPhone" while running my application;
so i can store user modify data !!

since i don't know the directory structure of iPhone ..

so please help me regarding this..
i request other reader as well ...

iPhone SDK Articles said...

@ravi

I am sorry I do not understand your question. Do you want to know the directory structure of the iPhone? If yes, the simulator stores all the files here
~/Library/Application Support/iPhone Simulator/User/

All the applications are stored in the application folder.

Please let me know if this helps.

Happy Programming,
iPhone SDK Articles

Anonymous said...

Great tutorial as always. I am having the same problems that past users have had and I'm not quite sure how to get around it. No data is displayed when I run it...

I have looked for ~/Library/Application Support/iPhone Simulator and this doesn't exist.

Any thoughts?

iPhone SDK Articles said...

@Anonymous

Is it possible for you to send me an email with the source code? I will be happy to take a look at it.

Happy Programming,
iPhone SDK Articles

Anonymous said...

Sure, I'll do that. Thanks.

Prince2k3 said...

I'm having trouble running the App on the iphone itself. It seeems to throw this error -- Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to create writable database file with message 'Operation could not be completed. No such file or directory'. ' -- when I run it on the simulator its fine. can anyone help

springrider said...

excellent tutorial! I did what you say and it works!
I have a question about the MVC model.
In the tutorial you said:" for simplicity I' am going to add the "SQLAppDelegate.h" header file in "SQL_Prefix.pch" file". What does this file mean? should we consider always include things within this file?
My understanding is that there is a more complex but usual way to do it but might be diffcult to understand?
I always thought ViewController is not actually controller but Views(in MVC), if that's right, maybe it's not a good habit to use "AppDelegate" in the "viewDidLoad".
Anyway, I would like to have a good and clear start on the MVC, hope you could help me to make it right. Great thanks!

Anonymous said...

Thank you very much for this Tutorial. It is being very helpful. I'm a beginner in xcode and sorry if my doubt were so basic.

I did everything with a lot of attention and carefully. But after review all my code I'm still getting the error: "__TERMINATING_DUE_TO_UNCAUGHT_EXCEPTION__"
I put a break point to debug the code and I see that this error occurs after the attempt to copy the db file to simulator. When I watch the file dbPath in debug it appears to be incomplete. The path that appears is not complete.
My database fie was named wrongly but even after it was renamed correctly and reimported the error persists.

I'm using a Mac OS 10.5.6 - Xcode 3.1.2
Could you help me?

Thanks in advance.

JLC said...

I've just realized what was going wrong.
I don't know why if I use stringByAppendingPathComponent:@"SQL.sqlite" in method getDBPath the DB was opened but I cannot run any sql statement. Whena I changed it to stringByAppendingString:@"SQL.sqlite" worked so far.

Do you know why did it happen?

Now the app is running ok. I can enter dta throughout Firefox plugin an this is showed on iPhone simulator.

Regards

iPhone SDK Articles said...

@Prince2k3 Your sqlite database file is stored in the "Resources" director, correct?
Can you send me your source code and I will be happy to take a second look.

Happy Programming,
iPhone SDK Articles

AngelHide said...

Hello,

@Prince2k3, I don't know if you found your problem, I had the same issue as you.

try this in "- (NSString *) getDBPath" method I was using the return as follow "return [documentsDir stringByAppendingFormat:@"SQL.sqlite"];"
no problem in the simulator but on the iphone I had the same issue.

I correct it by "return [documentsDir stringByAppendingPathComponent:@"SQL.sqlite"]; " and it works fine now

Anonymous said...

Hello

Thank you very much for the tutorial. I have made some adjustments to the code and in doing so I do not get the initial display for some reason. I can add to the list but when i close the application it is gone and never actually add to the SQL database. Is there any way you can help I would greatly appreciate it.

Anonymous said...

I had to add :
#import "SQLAppDelegate.h"
to the top of my Coffee.m file to get this example to work. You did not do this in your example code. I read the article many times to see if I missed something. Did I miss something? Please let me know.

Thanks,

David

The40Watt said...

I am having a problem with this. When I run the iPhone simulator the view shows up with Latte and Java on the screen. I didn't put them in my SQL.sqlite database.

Where did these values come from? Also items I put in my Coffee table don't show up.

Raj (www.lateinthearena.com) said...

Hi, A fantastic tutorial. Thanks a lot for putting it together. I have followed all the instructions and I could load the view. But, the load does not load the data from DB. I tried to debug with NSLog and here is the output
2009-04-25 13:39:36.393 SQL[712:20b] getInitialDataToDisplay Coffee Class
2009-04-25 13:39:36.398 SQL[712:20b] sqlite3_open
2009-04-25 13:39:36.405 SQL[712:20b] sqlite3_prepare_v2
2009-04-25 13:39:36.408 SQL[712:20b] my SQL = select coffeeID, coffeeName from coffee
2009-04-25 13:39:36.414 SQL[712:20b] ViewDidLoad - RootViewController

Here is the code that generated this log
NSLog(@"sqlite3_prepare_v2");
NSLog(@"my SQL = %s", sql);

while(sqlite3_step(selectstmt) == SQLITE_ROW) {

NSLog(@"sqlite3_step");
NSLog(@"my SQL = %s", sql);

My app. does not go in to the while loop. Am I missing something here?
Coffee table has 2 rows. Please help

Thanks,
Raj