Search your table's data

January 24th, 2011
#ui #core data

Adding search functionality to your UITableView can be achieved using the built in UISearchBar object and implementing the UISearchBarDelegate protocol. I am going to take us through an example that searches an Article instances (data object) properties for the users entered search string, displaying those articles that match. Each article object corresponds to a record in a database, this isn't needed for this example any data structure would be fine. For the sake of fullness the Article class is listed below:

@interface Article : NSManagedObject {
}

@property (nonatomic, retain) NSNumber * boolFavorite;
@property (nonatomic, retain) NSString * strResourceName;
@property (nonatomic, retain) NSString * strExtension;
@property (nonatomic, retain) NSString * strArticleTitle;
@property (nonatomic, retain) NSString * strDescription;
@property (nonatomic, retain) NSString * strImageName;

@end

Back to the search functionality:

.h

@interface ArticlesViewController : UIViewController  {
  NSArray *dataSource;
  NSMutableArray *tableData;
  UITableView *tableView;
}

@end

So in the above we have the original (full) dataset - dataSource. tableData is the data that will be updated to show those that match the user's search string and the tableView that will be updated itself. Important to note at this stage that this class is not a UITableViewController so the datasource and delegate methods are in another class.

I haven't used a xib in this example so that you can see all the source code needed and follow all the connections made in the one file. Below I shall show the snippets of code necessary to add search functionality to an already existing tableview setup.

.m

-(void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];

  if ([self.view viewWithTag:kTableViewTag] == nil) { //don't redraw if view already exists

  	UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 35)];
  	searchBar.delegate = self;
  	[self.view addSubview:searchBar];

  	tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, (searchBar.frame.size.height + searchBar.frame.origin.y), self.view.frame.size.width, (self.view.frame.size.height - (searchBar.frame.size.height + searchBar.frame.origin.y))) style:UITableViewStylePlain];
  	tableView.tag = kTableViewTag;
  	tableView.backgroundColor = [UIColor clearColor];

  	dataSource = [[CoreDataAccess getRecordsForTable:@"Article"] retain];

  	tableData = [dataSource mutableCopy];

  	ArticleTableController *tableViewController = [[ArticleTableController alloc] initWithDataSet:tableData];

  	tableView.dataSource = tableViewController;
  	tableView.delegate = tableViewController;

  	[self.view addSubview:tableView];
  	[tableView release];
  }
}

Of note in the above is the set up of the UISearchBar:

UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 35)];
searchBar.delegate = self;
[self.view addSubview:searchBar];

Nothing too difficult in the above, we are creating a UISearchBar, giving it a frame, setting its delegate and and adding it to the overall view. The next important section is below:

dataSource = [[CoreDataAccess getRecordsForTable:@"Article"] retain];

tableData = [dataSource mutableCopy];

ArticleTableController *tableViewController = [[ArticleTableController alloc] initWithDataSet:tableData];

In the above am getting my article objects, i.e. tableviews content from the database, set up my search array - tableData with a mutable copy of the original dataset and pass this mutable dataset as the tables content. This is an important step if you pass the original dataset than when you search you won't see any change to tableview, as it is this tableData that we will be updating. CoreDataAccess is a set of helper methods that I've created to simplify retrieving data through CoreData. It can be downloaded here

Time for the UISearchBarDelegate methods:

#pragma mark -
#pragma mark UISearchBarDelegate

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {

	searchBar.showsCancelButton = YES;
	searchBar.autocorrectionType = UITextAutocorrectionTypeNo;

	//empty previous search results
	[tableData removeAllObjects];
}

- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
	searchBar.showsCancelButton = NO;
}

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

	//empty previous search results
	[tableData removeAllObjects];

	if([searchText isEqualToString:@""] || searchText==nil){
		//show original dataset records
		[tableData addObjectsFromArray:dataSource];

		[tableView reloadData];
	}else {
		for(Article *art in dataSource){
			NSRange foundInTitle = [[art.strArticleTitle lowercaseString] rangeOfString:[searchText lowercaseString]];

			if(foundInTitle.location != NSNotFound){
				[tableData addObject:art];
			}else {

				NSRange foundInDescription = [[art.strDescription lowercaseString] rangeOfString:[searchText lowercaseString]];

				if(foundInDescription.location != NSNotFound){
					[tableData addObject:art];
				}
			}
		}

		[tableView reloadData];
	}
}

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {

	[tableData removeAllObjects];
	[tableData addObjectsFromArray:dataSource];
	[tableView reloadData];

	[searchBar resignFirstResponder];
	searchBar.text = @"";
}

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
	[searchBar resignFirstResponder];
}

Using the above methods you produce a case-insensitive search on Article object's two properties - strDescription and strArticleTitle

What do you think? Let me know by getting in touch on Twitter - @wibosco