Learning to Search (+SQLite queries)

Hello,

Today I’m starting to write tutorials for iPhone, starting with a simple thing that took me a while to learn. How to make simple searches for iPhone and display it promptly in the search result?

All the commands and screenshots below were taken on a MacOS X 10.6.2 with XCode 3.2.1 and iPhoneOS SDK 3.1.2.

I started by creating a database with common people names. I’ve found some common american names for male and female, wich I got the first 50 of each one, making a simple SQLite table, that you can get it from here (zipped for convenience).

The SQLite table structure is quite simple: it contains only one table names “names” with only one field named “name” (If you use MacOS X, you can check out the program Base to see the table structure).

So, start by creating a new iPhone project on XCode (window only) and name it whatever you like (I named mine “LearningToSearch”).

With our project created, we should add the sqlite3.dylib in our project, as we’re going to read the database from a sqlite database file. Control-click the Frameworks Folder -> Add -> Existing Framework. From the list, select libsqlite3.dylib.

With that done, let’s move on on creating a table for display our data, but we need to create a TableView. For that, Create a new file. Menu File -> New File… -> iPhone OS/Cocoa Touch Class -> UIViewController Subclass. Be sure to mark both checkboxes on Options (UITableViewController subclass and With XIB for user interface, as you can see in the screenshot below).

Name it whatever you like. I named mine “SearchView”. It will create 3 files for you with extensions h, m and xib. We’re going to need a pointer to the TableView when doing our search, so, let’s create an outlet for holding it. In your SearchView.h file (or whatever you named it), add a variable and a property, like so:

@interface SearchView : UITableViewController {
	UITableView *myTableView;
}
@property(nonatomic,retain) IBOutlet UITableView *myTableView;

That will hold our pointer to our myTableView that we’ll setup later on Interface Builder. Synthesize the getters and setters by adding the following code on SearchView.m after the @implementation line:

@synthesize myTableView;

That’s it for now. We’ll be back for them later, let’s now setup some properties on the main application, add a import directive for our SearchView.h and getters and setters for our SearchView controller like we did with the table view. On “LearningToSearchAppDelegate.h”:

#import <UIKit/UIKit.h>
#import "SearchView.h"
 
@interface LearningToSearchAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    SearchView *viewController;
}
 
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet SearchView *viewController;
 
@end

With that, a synthesize in our .m file:

@synthesize viewController;

That’s it we’ll come back later here too, but now let’s setup our interface.

Setting up the interface

Open MainWindow.xib in the Interface Builder (just double-click it).

You’ll get a blank interface screen with another window with 4 objects


Next, we’ll add a UIViewController in our project. Select it and drag it along with the four other icons.

On “View Controller attributes” (Command-1) change NIB Name to “SearchView”

And on “View Controller Identity” (Command-4), change the Class parameter to “Search View”

Now, select “Learning To Search App Delegate” and link the viewController in the “Connection Inspectors” (Command-2) to the Search View Controller object.

We’re done with the main view. It does not much other then loading our SearchView. Save the file and open the “SearchView.xib” file. You’ll be presented with a table with some sample data.

Your window will have only three objects, the File’s Owner, First responder and Table View. We need to add a UISearchBar to the table. Just drag it from the Library to the Table. It’ll be positioned in the top of the table by default. Now, we just need to make two connections.

1) With the SearchBar selected, drag a link from “delegate” to the “File’s Owner” object. With this, we’re telling the SearchBar to send all events to our SearchView class.

2) With “File’s Owner” selected, drag a link from the “myTableView” outlet to the “Table View” object.

Our GUI setup is done. You can play around with the Search Bar parameters in the inspector, but nothing else is required for our sample to work.

Setting up the code

Now, to the code. We need to do some things:

1) Make our SearchView appear in the MainWindow (the links are set, but nobody told anyone to show it!);
2) Load our database into an array;
3) Setup the code for the table display our data
4) Setup the search delegate (wich is the SearchView class) to understand and treat the Search Bar events.

Step 1:

This step is easy. Find the “LearningToSearchAppDelegate.m” file and add this line to the beggining of the “applicationDidFinishLaunching:” function:

    [window addSubview:viewController.view];

Note: If you run your program now, you should see an empty table with a search bar on the top.

Step 2:

On to the database.

Create two NSMutableArray variables on the SearchView.h file. One that will hold all our names, the other that will hold our search result (include these inside the brackets on the interface declaration).

	NSMutableArray *searchedNames;
	NSMutableArray *names;

No need to setup getters and setters for these, as we’re only going to use tem internally. We need to initialize our variables before we can use them. For that, we add the following lines in the “viewDidLoad” function (in the end of the function):

	names = [[NSMutableArray alloc] init];
	searchedNames = [[NSMutableArray alloc] init];

Now, to the hard part: Load the database and store all the data inside the names variable.
Actually, that’s not hard at all, just copy that (hopefully documented) function below and place it in your SearchView implementation (no need to declare it on our header file, since we’re only going to use it internally):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
-(void)loadNames {
	// Path to the database
	NSString* dbPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"people_names.sqlite"];
	NSLog(@"databasePath: %@",dbPath);
	sqlite3 *database;
	NSString *name;
 
	// Open the database
	if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK) {
		const char *sql = "SELECT * FROM names";
		sqlite3_stmt *compiledStmt;
		// Fetch all names
		if (sqlite3_prepare_v2(database, sql, -1, &compiledStmt, NULL) == SQLITE_OK) {
			// Append each name
			while (sqlite3_step(compiledStmt) == SQLITE_ROW) {
				const char* cName = (char*)sqlite3_column_text(compiledStmt, 0);
				if (cName == NULL)
					// There should not be a NULL name
					NSLog(@"Null name!!");
				else {
					name = [NSString stringWithUTF8String:cName];
					[names addObject:name];
					// Apparently the addObject function in NSMutableArray does not
					// keep a copy of our object, so, we can't release it.
					//[name release];
				}
			}
			sqlite3_finalize(compiledStmt); // Cleanup the statement
		}
		else {
			NSLog(@"Error retrieving data from database.");
		}
		sqlite3_close(database);
	}
	else {
		NSLog(@"Error: Can't open database!");
	}
}

Great! Now what about the database file? If you compile it as it is now, you’ll not read anything since our database is nowhere to be found. Drag the (unzipped) people_names.sqlite file to the “Resources” folder in XCode.

One more thing until we’re done with the SQLite part: we need to call our “loadNames” function. We’re going to alter our “viewDidLoad” function again. It’ll be like this:

- (void)viewDidLoad {
    [super viewDidLoad];
 
	names = [[NSMutableArray alloc] init];
	searchedNames = [[NSMutableArray alloc] init];
	[self loadNames];
}

Step 3:

Now we need to make our class respond to the TableView calls. Since we’re using the “searchedNames” array to store our data, we need to make sure we return the information from there. The TableView calls are quite self-explanatory, so, here goes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#pragma mark Table view methods
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
	return(1);
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
	return([searchedNames count]);
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
 
    // Set up the cell...
    NSString *cellText = [searchedNames objectAtIndex:indexPath.row];
    [cell.textLabel setText:cellText];
 
    return cell;
}

In short:
* We have only one section
* The number of lines is the same number of the searchedNames stored objects
* When the table asks for a cell, we get the name of the object on the searchedNames and put it in the cell

Step 4:

Now to the sweet part. We need to answer the calls from the SearchBar. We want the “Search as we type” kind.

Here’s our full code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#pragma mark Search Functions
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
}
 
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
}
 
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
	[searchedNames removeAllObjects];// remove all data that belongs to previous search
	if([searchText isEqualToString:@""] || searchText==nil) {
		// Nothing to search, empty result.
		[myTableView reloadData];
		return;
	}
 
	for (NSString *name in names) {
		NSRange r = [name rangeOfString:searchText options:NSCaseInsensitiveSearch];
		if(r.location != NSNotFound) {
			[searchedNames addObject:name];
		}
	}
	[myTableView reloadData];	
}
 
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
}
 
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
	[searchBar resignFirstResponder];
}

Now, we don’t care when the user begin editing, ends editing or clicks the cancel button. We don’t really have a cancel button (unless you changed that), and we are using “on-the-go” searching.

The last function is important to make room when the user finishes searching. It disposes of the keyboard.

The “searchBar:textDidChange:” function is where we do our hardwork. We search in every item of our “names” arrange for an occurrence of the text in the SearchBar. Passing the option “NSCaseInsensitiveSearch”, we don’t mind for lower-case or upper-case letters.

We’re done!

Compile your project.

Check it our in your simulator.

Put it on your iPhone to play around on-the-go (if you have the license).

It took me a lot of searching to learn to do those, I hope you have less pain than me. Be sure to drop me a note if you have any questions.

Until next time!

This entry was posted in iPhone, Tutorials and tagged , , , | permalink | RSS feed for this post. Post a comment or leave a trackback: Trackback URL.

3 Comments

  1. Michele
    Posted May 3, 2010 at 5:13 am | Permalink

    Ciao,
    ho provato a seguire il tuo tuttorial e non riesco a farlo funzionare.
    Il programma viene compilato ed eseguito, ma non mi carica i dati in tabella. Ho Visto che non vengono collegati i metodi della TableView.
    Cosa potrei controllare?

    Grazie mille
    Michele

    P.S. Scusami per il mio inglese

  2. Michele
    Posted May 3, 2010 at 5:33 am | Permalink

    Hello,
    I tried to follow your tuttorial and I can not get it to work.
    The program compiles and runs, but I do not load the data table. Since I do not link the methods of Tableview.
    What could I check?

    Thank you very much
    Michele

    P.S. Sorry for my English

  3. Posted May 4, 2010 at 8:57 am | Permalink

    @Michele
    Have you properly made the links to the ViewController using the Interface Browser, as described above Now, select “Learning To Search App Delegate” and link the viewController in the “Connection Inspectors” (Command-2) to the Search View Controller object.?
    Is your controller a subclass of TableViewController?
    Does your program crash or returns a blank table?
    Have you added the file people_names.sqlite (unzipped) to your X-Code project?

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">