Though UISearchDisplayController is seemingly easy (and yes it’s easy), apart from the sample source code, there isn’t much documentation available from Apple. I won’t be posting code for this tutorial, (as most of them come from Apple’s own source code), however, the tutorial will contain code fragments that I wish to highlight and those I changed for improving the search using NSPredicate
Step 1: Create the project
If you haven’t created a project yet, create a Navigation Based Project and open the RootViewController.xib.
Step 2: Modify the XIB
Drag UISearchDisplayController to your RootViewController.xib. (It automatically does the connections for you) Drag the SearchBar that gets added onto your TableView. You need the UISearchBar to be displayed on the controller. So drag the searchbar and place it on the top of the table view. Add ScopeBars if you need any. We will discuss later how to handle the scope bar changes and how to filter text based on the selected scope.
Step 3: The Design of UISearchDisplayController
In a traditional tableviewcontroller, you normally have one master list of data which you use for all the tableview data source methods. In case of UISearchDisplayController, you have to maintain two lists. One master list and one a filtered list. The tableview data source delegates will be called by the framework when it needs to populate your tableview or the search results. So in all the methods you have to differentiate whether the data source call is for populating the filtered list or for populating the main list.
This is done by a simple two line code.
if (tableView == self.tableView) { // normal table view population } if(tableView == self.searchDisplayController.searchResultsTableView){ // search view population }
This code should be written in cellForRowAtIndexPath, didSelectRowAtIndexPath and numberOfRowsInSection.
For the complete code listing, see Apple’s sample code.
Step 3: Implement the search delegates
When the user invokes a search,the UISearchDisplayDelegate functions you implement will be called. The two main functions that you have to implement are,
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
Copy the implementation from Apple’s source.In the header file, your class should implement UISearchBarDelegate and UISearchDisplayDelegate.
As the name suggests, the first function will be called when the user changes the scope button and the second function is called when he starts typing a search query. The first function is not necessary if you haven’t added any scope button in step 2.
Step 4: Filtering your data using NSPredicate
This step is the reason why I wrote this seemingly simple post. I didn’t like the Apple’s way of filtering. It doesn’t match “contains” text. I modified the filterContentForSearchText method, so as to use NSPredicate. You still need the Apple’s implementation of shouldReloadTableForSearchString and shouldReloadTableForSearchScope.
The filterContentForSearchText implementation CANNOT be copy-pasted into your application. It’s a code fragment that doesn’t compile without some changes. Points to note in the function is the use of predicate.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(SELF contains[cd] %@)", searchText];
The [cd] in the predicate format means case and diatric insensitive. the verb I have used is “contains”, you can change it to beginsWith, endsWith, like and many others. The NSPredicate is a very powerful feature that can be exploited here. I don’t understand why Apple just didn’t use it
For more info, look at Apple’s NSPredicate documentation here
Below is the complete code listing after making the NSPredicate changes.
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope { [self.filteredData removeAllObjects]; // First clear the filtered array. for (Product *product in tableData) { NSPredicate *predicate = [NSPredicate predicateWithFormat: @"(SELF contains[cd] %@)", searchText]; [product.productID compare:searchText options:NSCaseInsensitiveSearch]; BOOL resultID = [predicate evaluateWithObject:product.productID]; BOOL resultName = [predicate evaluateWithObject:product.productName]; if([scope isEqualToString:@"Product ID"] && resultID) { [self.filteredData addObject:product]; } if([scope isEqualToString:@"Product Name"] && resultName) { [self.filteredData addObject:product]; } if([scope isEqualToString:@"Any"] && (resultID || resultName)) { [self.filteredData addObject:product]; } } }
With NSPredicate, you can play around with the search filters. NSPredicate is as powerful and easier to use than Regular Expressions. Try your hand at it.
–
Mugunth
Related posts:
- iPhone Tutorial – Enabling reviewers to use your In-App purchases for free In-App purchases is a great way for developers to upsell...
- iPhone Tutorial – In-App Purchases Last week, Apple announced that in-app purchases will be available...
- iPhone Tutorial: Follow Cost API and a open source wrapper What is Follow Cost? Follow Cost is a interesting and...
- iPhone Tutorial – In-App Email codeproject Sending an email from your iPhone application is...
- iPhone Tutorial: Elegant way to send formatted In-App email By now, most of you know how to send emails...
There are 8 ads in that short block of code
try it now… I'm sorry.
First Of All, let me commend your uncloudedness on this subject. I am not an expert on this issue, but after learning your article, my understanding has developed substantially. Please permit me to grab your rss feed to stay in touch with any forthcoming updates. Complete job and will extend it on to friends and my website fans.
There are a few little details that would make this better.
Look at -[NSArray filteredArrayUsingPredicate:(NSPredicate *)predicate] and you won't need that loop. In any case, there is no need to make a new predicate identical to the last one each time through that loop. You should probably put it outside of the loop.
I also suggest that you consider modifying the predicate you are filtering with, rather than filtering using the if([scope isEqualToString...] bits. For example:
if([scope isEqualToString:@"Product ID"]) {
predicate = [NSPredicate predicateWithFormat:@"(SELF.productID contains[cd] %@)", searchText];
}
then the filtered array is just:
[tableData filteredArrayUsingPredicate:predicate]
so you won't need a loop.
You can even drop an AND or an OR into your predicate or just use NSCompoundPredicate's various class methods to make your filter more dynamic.
See Apple's Predicate Programming Guide for more details