Search Your Table's 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 the properties of each Article instance (data object) for the user's search string, displaying those articles that match. Each article object corresponds to a record in Core Data; this isn't needed for this example, as any data structure would be fine. For the sake of completeness, 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, starting with the interface of ArticlesViewController:
@interface ArticlesViewController : UIViewController <UISearchBarDelegate>
{
NSArray *dataSource;
NSMutableArray *tableData;
UITableView *tableView;
ArticleTableController *tableViewController;
}
@end
So in the above code snippet, 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 tableView is the table view that will be updated. It's important to note at this stage that this class is not a UITableViewController, so the data source and delegate methods are in another class - ArticleTableController - which tableViewController holds onto.
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 table view setup, starting with viewWillAppear::
@implementation ArticlesViewController
- (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, self.view.frame.size.width, 35)];
searchBar.delegate = self;
[self.view addSubview:searchBar];
[searchBar release];
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];
tableViewController = [[ArticleTableController alloc] initWithDataSet:tableData];
tableView.dataSource = tableViewController;
tableView.delegate = tableViewController;
[self.view addSubview:tableView];
[tableView release];
}
}
- (void)dealloc
{
[tableViewController release];
[tableData release];
[dataSource release];
[super dealloc];
}
@end
Of note in the above is the setup of the UISearchBar:
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 35)];
searchBar.delegate = self;
[self.view addSubview:searchBar];
[searchBar release];
Nothing too complicated in the above code snippet; we are creating a UISearchBar, giving it a frame that matches the width of the view, setting its delegate, and adding it to the overall view. Once added, the view retains the search bar, so we can release our reference to it.
The next important section is below:
dataSource = [[CoreDataAccess getRecordsForTable:@"Article"] retain];
tableData = [dataSource mutableCopy];
tableViewController = [[ArticleTableController alloc] initWithDataSet:tableData];
In the above, I am getting my article objects, i.e. the table view's content, from Core Data, setting up my search array - tableData - with a mutable copy of the original dataset and passing this mutable dataset as the table's content. This is an important step: if you pass the original dataset, then when you search, you won't see any change to the table view, as it is tableData that we will be updating. As a table view doesn't retain its data source or delegate, we hold onto tableViewController in an ivar and release it, along with the datasets, in dealloc.
CoreDataAccessis a set of helper methods that I created to simplify retrieving data through Core Data. It eventually morphed intoCoreDataServices, which is available as a pod.
Time for the UISearchBarDelegate methods:
@implementation ArticlesViewController
// Omitted other functionality
#pragma mark -
#pragma mark UISearchBarDelegate
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
searchBar.showsCancelButton = YES;
searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
// empty previous search results
[tableData removeAllObjects];
[tableView reloadData];
}
- (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];
}
@end
When
searchBarTextDidBeginEditing:emptiestableData, it also reloads the table so that the table view never asks for a row that no longer exists.
Using the above methods, you produce a case-insensitive search on Article object's two properties - strDescription and strArticleTitle.