Tuesday, February 24, 2009

iPhone Development: Adding Search Bar in Table View

In this post I will be creating a Search screen, which will have a table view with a search bar. Table should display all the records if search field is empty other wise it should show all the records that have matching strings with the search field.

At the end, it should look like this:


So to start create a new ViewBased Project and name it as MySearchScreenApp. You can start with any template you want but you can find in my previous posts why I always start with the most basic template.

The Search screen that you can see in the above images are implemented in MySearchScreenAppViewController class. So open the header file MySearchScreenAppViewController.h and add the following code:

@interface MySearchScreenAppViewController : UIViewController {

UITableView *myTableView;

NSMutableArray *dataSource; //will be storing all the data

NSMutableArray *tableData;//will be storing data that will be displayed in table

NSMutableArray *searchedData;//will be storing data matching with the search string

UISearchBar *sBar;//search bar


}

@property(nonatomic,retain)NSMutableArray *dataSource;


@end


Here we have only defined the UIElements and some data sources. There are three mutable arrays. dataSource will be containing all the data and will be initialized before loading the searchScreen. searchedData will be holding a subset of dataSource based on search string. It will be contents of tableData which you will be viewing in the table below searchBar.

To implement all these elements, open MySearchScreenAppViewController.m file and add the following code in loadView:

- (void)loadView {

[super loadView];

sBar = [[UISearchBar alloc]initWithFrame:CGRectMake(0,0,320,30)];

sBar.delegate = self;

[self.view addSubview:sBar];

myTableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 31, 300, 400)];

myTableView.delegate = self;

myTableView.dataSource = self;

[self.view addSubview:myTableView];

//initialize the two arrays; dataSource will be initialized and populated by appDelegate

searchedData = [[NSMutableArray alloc]init];

tableData = [[NSMutableArray alloc]init];

[tableData addObjectsFromArray:dataSource];//on launch it should display all the records

}



The only code worth explanation here is the last line [tableData addObjectsFromArray:dataSource]
As already discussed, dataSource will be holding all of data and tableData will be holding data to be shown in table. This code copies all elements of dataSource into tableData so that when the search screen is visible first time, all the data is displayed.

Now we implement all the tableViewDelegate methods: In the same file add the following code:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

return 1;

}



- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

//NSLog(@"contacts error in num of row");

return [tableData count];

}



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

static NSString *MyIdentifier = @"MyIdentifier";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];

}

cell.text = [tableData objectAtIndex:indexPath.row];

return cell;

}


If you do not understand the code above, you should refer my earlier posts. These methods will be displaying data in tableData array into our table view.
Now we have to implement search bar delegate methods, which will be controlling the contents of tableData. In the same file add the following code:

#pragma mark UISearchBarDelegate


- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar

{

// only show the status bar's cancel button while in edit mode

sBar.showsCancelButton = YES;

sBar.autocorrectionType = UITextAutocorrectionTypeNo;

// flush the previous search content

[tableData removeAllObjects];

}


- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar

{

sBar.showsCancelButton = NO;

}


In the first method, we are only changing the appearance of search bar when a user taps on search bar. Refer the first two images to find the difference. The main code will be written now:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText

{

[tableData removeAllObjects];// remove all data that belongs to previous search

if([searchText isEqualToString:@""]||searchText==nil){

[myTableView reloadData];

return;

}

NSInteger counter = 0;

for(NSString *name in dataSource)

{

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];

NSRange r = [name rangeOfString:searchText];

if(r.location != NSNotFound)

{

if(r.location== 0)//that is we are checking only the start of the names.

{

[tableData addObject:name];

}

}

counter++;

[pool release];

}

[myTableView reloadData];

}


This method will be called whenever text in the search bar is changed. We should update tableData array accordingly. So the first line[tableData removeAllObjects]; removes all the searched records from previous search string.
Then we check if the search string is null so that we should return immediately with and empty tableData.
If the searchString is not null then we will go through each object in dataSource and select those objects which have the occurrence of search string in beginning. You can modify the code to have any kind of search though.
The last line[myTableView reloadData]; refreshes the table view. What ever be the content of tableData will be shown now.

We should also show all the records if the search string is cleared or cancelled. For that add the following code:

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar

{

// if a valid search was entered but the user wanted to cancel, bring back the main list content

[tableData removeAllObjects];

[tableData addObjectsFromArray:dataSource];

@try{

[myTableView reloadData];

}

@catch(NSException *e){

}

[sBar resignFirstResponder];

sBar.text = @"";

}


// called when Search (in our case "Done") button pressed

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar

{

[searchBar resignFirstResponder];

}


In the first function, we have flushed all the searched records and simply copy all the records from dataaSource. In the second function we are telling the text pad to resign. You can skip this method to see what happens.


Thats it!! We are done with the search screen. But we have to initialize it from AppDelegate. So open MySearchScreenAppAppDelegate.m and add the following code:

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

// Override point for customization after app launch

viewController.dataSource = [[NSMutableArray alloc]init];

for(char c = 'A';c<='Z';c++)

[viewController.dataSource addObject:[NSString stringWithFormat:@"%cTestString",c]];

UINavigationController *nvc = [[UINavigationController alloc]initWithRootViewController:viewController];

viewController.title = @"Search";

[window addSubview:nvc.view];

[window makeKeyAndVisible];


Here we have only added dummy data into dataSource to test. You can always store data from database or anywhere .

Now build and run the application. Try typing characters in SearchField, you will find that the search is case sensitive. You will also notice that the search works only if you type the beginning characters only. If you want that the searched records should have search string occurance at any place, here is an alternative implementation of (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText

{

[tableData removeAllObjects];// remove all data that belongs to previous search

if([searchText isEqualToString:@""]||searchText==nil){

[myTableView reloadData];

return;

}

NSInteger counter = 0;

for(NSString *name in dataSource)

{

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];

NSRange r = [name rangeOfString:searchText];

if(r.location != NSNotFound)

[tableData addObject:name];

counter++;

[pool release];

}

[myTableView reloadData];

}


I hope this post was useful. I will suggest you shou should read Predicates to optimize the search if you have very large data source to be searched.

Comments will be appreciated.



30 comments:

  1. Do you have xcode project code for this?

    ReplyDelete
  2. amm..i will have to find...will upload it on monday

    ReplyDelete
  3. Great Tutorial... Thank you very much.

    For some reason, though.. I can't get the data to load into the table when the app is initialized. It only loads after I hit the cancel button on the search bar. the [tableData addObjectsFromArray:dataSource] in the loadview method seems like it should do the trick right?

    Anybody have any ideas?

    ReplyDelete
  4. Ved ... Can u please upload the code ??

    ReplyDelete
  5. I would love the source code for this tutorial. Thanks!

    ReplyDelete
  6. the style in your blog is sadly nearly unreadable. anyway thx for this blog-entry.

    ReplyDelete
  7. Hi,
    I get two errors with my viewController, (viewController.dataSource...). Here is the message : error 'ViewController' undeclared (first use in function).
    Why this problem ?
    Thanks

    ReplyDelete
  8. Hi, sorry for the late reply, I have been bzzy with other tasks.
    A lot many things have changed in 3.0/3.1

    Please check if in the .h file viewController is injected by default or not. If it is something else, use the same name.

    ReplyDelete
  9. Could you please explain why you're using NSAutoreleasePool here?

    ReplyDelete
  10. I don think there is much need of autorelease pool there, but at the same time its harmless and can be helpful too.When the database is huge like in crm applications, the for loop might iterate large number of times and we are crating NSRange auto release objects for every iteration, so a new pool will ensure these objects are released immediatly after every iteration rather than stacking all the objects untill for loop ends and release them in next pool.
    Although I am not sure removing the pool from there and testing it with huge database will cause any problems or not but atleast it gives certainity and assurance :)

    ReplyDelete
  11. How do I have the searchbar hidden underneath the navbar when it first comes up?

    ReplyDelete
  12. you can try removing it from the view and add when required.

    You can also try changing the alpha value to 0.

    ReplyDelete
  13. How do i do it if my source of data is from a plist file?can anyone help?

    ReplyDelete
  14. I already have a text related app on iTunes. One thing it lacks is a search feature.

    I need help. I am a novice to developing. Someone developed it for me and wants to charge an arm and a leg for this feature.

    If you know anyone will to takes this task for a reasonable price I would appreciate it. Already spent a lot to develop the app. It NOW NEEDS A SEARCH FEATURE TO SEARCH WITHIN THE APP.

    Thanks

    ReplyDelete
  15. @Learner, I am really short of time these days but send over your use case n ll try helping.

    ReplyDelete
  16. I received an error " class does not implement the UISearchBarDelegate/UITableViewDelegate " in MySearchScreenAppViewController until I added " to the MySearchScreenAppViewController.h file like so

    example:
    @interface MySearchScreenAppViewController : UIViewController
    {....

    ReplyDelete
  17. This blog does not show code within greater/less than signs.. here are the delegate protocols I added to the MySearchScreenAppViewController.h header file

    UISearchBarDelegate, UITableViewDelegate, UITableViewDataSource

    ReplyDelete
  18. @above: well thanks for mentioning, ll check wat is the issue

    ReplyDelete
  19. Great post!
    thx, thimon

    ReplyDelete
  20. wow, this code is full of bugs and the author don’t care for fixing them.
    Amazing! If you were lazy to create a decent post you shouldn’t.

    ReplyDelete
  21. @above: I ll suggest u to quit iPhone dev bt thas upto u :P

    ReplyDelete
  22. How do we make the search bar float and not scroll with the table view?

    ReplyDelete
  23. one way is to add searchBar as a separate subview.

    so u have a view..on which u add a searchbar as subview say 40px tall and den add your tableview with top offset of 40px

    ReplyDelete
  24. Interesting post!!!! Thanks for sharing code for creating search bar. I am also an iphone application developer. I will try your code to create search bar.

    iPhone Application Development

    ReplyDelete
  25. Well exposure of the application. When your apps get in the store, Apple will decide weather it will stay there or not.

    iphone developer

    ReplyDelete
  26. Hi

    Does anyone know how to parse it with XML and make the data from the XML load in the searchbar?

    Thanks,
    Aaron

    ReplyDelete
  27. i copy and paste your data into my application but it is not working.... i don't know why....please specify good data.....

    ReplyDelete
  28. To compare case insensitive, change the line to:

    NSRange r = [name rangeOfString:searchText options:NSCaseInsensitiveSearch];

    Very usefull code, it differs from others that is all programatically made.

    ReplyDelete

Leave your suggestions