RSS

Core Data Basics Part 7 – Search Bars

05 Mar


Introduction

If you’ve used an iPhone or iPad before you’ve probably used a Search Bar. This tutorial will show how to add a Search Bar to your project.  Again we will be using the Staff Manager project from previous parts of this tutorial set.  Specifically we will be adding the Search Bar to the Roles Table View of the Staff Manager project. Here’s what we’re aiming at:

Prerequisites

We follow on from Part 6, so download the project from the end of Part 6 then extract and open it with Xcode.

Adding a Search Bar

Select MainStoryboard.storyboard and find the Roles Table View Controller on the canvas. Drag a Search Bar and Search Display Controller on to the Roles Table View.  Be careful not to drag it into the prototype cell.

The Roles Table View should now look like this with a Search Bar:

Run the app and go to the Roles tab. Start typing in the Search Bar and you will notice the table view does not filter out data which doesn’t match the search criteria. Useless!

Making the Search Bar Useful

The data in the Table View should be filtered to show search results as a user types in the Search Bar. In order for this to work we need to set up the RolesTVC class to implement the UISearchDisplayDelegate and UISearchBarDelegate protocols. Update the following line in RolesTVC.h:

@interface RolesTVC : CoreDataTableViewController <AddRoleTVCDelegate, RoleDetailTVCDelegate, UISearchDisplayDelegate, UISearchBarDelegate>

 

To display a filtered subset of data we need to store the search results in an array. What better place than in a  searchResults NSMutableArray!  Add the following property to RolesTVC.h:

@property (nonatomic, retain) NSMutableArray *searchResults;

 

Of course, you will then need to add the following to RolesTVC.m:

@synthesize searchResults;

 

Also, create the following methods in RolesTVC.m.  They are responsible for initial setup and removal of the searchResults array:

- (void)viewDidLoad 
{
    self.searchResults = [NSMutableArray arrayWithCapacity:[[self.fetchedResultsController fetchedObjects] count]]; 
    [self.tableView reloadData];
}
 
- (void)viewDidUnload
{
	self.searchResults = nil;
}

 

So far in the tutorial series the CoreDataTableViewController has shielded us from needing to configure the numberOfRowsInSection method required by a TableView. This method simply tells the table how many rows to display.  The answer will be different depending on whether we are showing all data or only searchResults.  Add the following method to RolesTVC.m:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	if (tableView == self.searchDisplayController.searchResultsTableView)
	{
        return [self.searchResults count];
    }
	else
	{
        return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
    }
}

 

Likewise, the existing cellForRowAtIndexPath method also needs to differentiate between data from the searchResults and otherwise.  Change the cell configuration in the cellForRowAtIndexPath method within RolesTVC.m as follows:

    // Configure the cell...
    Role *role = nil;
 
	if (tableView == self.searchDisplayController.searchResultsTableView)
	{
        NSLog(@"Configuring cell to show search results");
        role = [self.searchResults objectAtIndex:indexPath.row];
    }
	else
	{
        NSLog(@"Configuring cell to show normal data");
        role = [self.fetchedResultsController objectAtIndexPath:indexPath];
    }

 

The next method is where most of the search filter magic happens. When you adapt the code to your own needs you will need to know what is happening here.  Here’s the overview:

  1. All searchResults are removed.
  2. A role object is created for every object fetched from Core Data.
  3. A role object is put into the searchResults array for all roles with a role name that matches searchText.

Add the following method to RolesTVC.m:

#pragma mark -
#pragma mark Content Filtering
 
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
	NSLog(@"Previous Search Results were removed.");
	[self.searchResults removeAllObjects]; 
 
	for (Role *role in [self.fetchedResultsController fetchedObjects])
	{
		if ([scope isEqualToString:@"All"] || [role.name isEqualToString:scope])
		{
			NSComparisonResult result = [role.name compare:searchText 
                                                   options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) 
                                                     range:NSMakeRange(0, [searchText length])];
            if (result == NSOrderedSame)
			{
                NSLog(@"Adding role.name '%@' to searchResults as it begins with search text '%@'", role.name, searchText);
				[self.searchResults addObject:role];
            }
		}
	}
}

Finally add these two additional small methods to RolesTVC.m:

#pragma mark -
#pragma mark UISearchDisplayController Delegate Methods
 
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
    [self filterContentForSearchText:searchString scope:@"All"];
    return YES;
}
 
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption
{
    [self filterContentForSearchText:[self.searchDisplayController.searchBar text] scope:@"All"];
    return YES;
}

Note that I haven’t shown you how to configure search scopes or anything fancy. I didn’t want to show too much at once. If you’re comfortable with the basics, check out Apple’s TableSearch project to learn more.

Run the app now and you should be able to filter the Roles using the Search Bar:

_______

That’s it!

Here’s the complete source code so far

If you liked this tutorial or found something wrong with it please let me know!

If you want to support my work and have an iPad please consider purchasing iSoccer *wink*

-Tim

Go to Part 8 or the Tutorials Index


Be Sociable, Share!
 

About Tim Roadley

I'm a 30 something year old married-with-children Infrastructure Manager living in Australia and working at Cuscal. I love iOS coding and have found the most effective way to learn (and retain) is by writing tutorials. I love getting feedback and helping like minded people learn iOS too :)
43 Comments

Posted by on March 5, 2012 in iOS Tutorials

 

43 Responses to Core Data Basics Part 7 – Search Bars

  1. Abhishek Sonawane

    March 5, 2012 at 9:16 pm

    Awesome tutorial Tim!!

    Amazing and super easy!!

    Cheers,
    Abhishek.

     
    • John Hebebrand

      March 4, 2013 at 4:16 am

      Although most all of these Core Data tutorials are spot on, the search code does not function as described. Seong got me most of the way there…selecting and segueing to the detail object. However when multiple items are returned with the search it always segues to the first item in the list. Has anyone figured out the search example and can you post the code? Thanks John

       
      • Graham Gardiner

        April 6, 2013 at 5:12 pm

        Hi john Hebebrand Did you work out what was to be done in the code to sort the problem of sending the top or first item on the search list? I have been trying to work it out. Please let me know if you have worked this problem out. Tim thank you for your great tutorials.

         
  2. Adrian

    March 6, 2012 at 1:12 pm

    Hi Tim
    Great tutorial pal. Very useful man. I see you changed the mobile theme on your web site. Pretty cool, pretty cool indeed. I like the way you use the pre loaded data in core data example bro. There are so many tuts in core data in ios 5 out there but are mostly generic stuff right out of apple docs , yours has a nice and easy approach. I hope everyone else enjoy following them as I do. Keep up the good work and be blessed.

    Adrian

     
  3. Maksanzhi

    March 6, 2012 at 8:46 pm

    Hi Tim,

    Great tutorial!
    I am a beginner in ios,
    can you make next tutorial: how to add to Favorites?

    Thank you

     
    • Tim Roadley

      March 7, 2012 at 8:19 pm

      Add favourites? like in safari? no idea!!

       
  4. Maksanzhi

    March 11, 2012 at 6:56 am

    I mean bookmarks ) sorry my english is very bad

     
    • Tim Roadley

      March 11, 2012 at 8:22 am

      I’m still unsure what you’re trying to do. You want to add an item from a tableview in to your safari bookmarks? I don’t think that’s possible? Do you have examples from google searches of what you want to do?

       
  5. Christina

    March 11, 2012 at 9:38 am

    Hi Tim,

    Tutorial looks to be JUST what I need for an app I have been struggling with in core data. Thanks, fellow Aussie!

     
    • Tim Roadley

      March 11, 2012 at 8:49 pm

      Glad you find them useful, good luck with you app!

      -Tim

       
      • shibi

        December 27, 2012 at 5:37 pm

        thanks

         
  6. santiago

    March 21, 2012 at 10:39 pm

    Hi Tim,

    great Job. Many thanks. I have one doubt. You see that the table view cells with the results are diferent to your custom previous ones. Could they have the same format and funcionality (go to modify the role)?

    Santi

     
  7. Seong

    March 23, 2012 at 4:26 pm

    Hello Tim, I am a fan of yours.
    I succeeded this searchbar work easily by ur kind help.
    But when I touch a cell filtered, the segue to the detailview does not perform. Its untouchable.
    Have I missed something?
    Thank you again anyway.

     
  8. Seong

    March 25, 2012 at 7:15 pm

    I ve got to know, the tableview that searchdisplaycontroller ceated is a new vanilla one so it doesnt hear anything other than just displaying data. So I ve tried to tell him to work ”didselectrow -> perfom the segue -> and show detailTVC with searchedresult’ . I should have told which data to show in preparing segue, so I tried to say if(seached), show searchedresult, else just show selected index row. But I ve failed to say ‘if(seached)’ condition. Maybe I m lost. Thank you as always.

     
  9. Seong

    March 27, 2012 at 2:12 am

    At last! I found the way. First, I wanna say Thank you so much, Tim. I ve grown up by your tutorials.
    This is how I succeed:
    at cellForRowAtIndexPath, UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier]; i.s. self. is the point. It makes the tableviewcells those searchdisplaycontroller made to look and to work just like the original tableviewcells that I made. Of course Segue is performed too.
    And when prepareForSegue,
    if (searchResults)
    self.selectedDbPx = [self.searchResults objectAtIndex:indexPath.row];

    else
    self.selectedDbPx = [self.fetchedResultsController objectAtIndexPath:indexPath];

    shows the exact detail view for the searched result cells.
    Thata’s all that I needed!
    It’s been so hard 3 days for me thou.
    Than you again, Tim.

     
  10. Seong

    March 27, 2012 at 3:11 am

    Sorry but the solution above I wrote missed something. not works properly. More investigation is needed.

     
    • santiago

      March 27, 2012 at 4:58 am

      Hi Seong,
      I have tried many things and the only solution I have found is this:
      cell = [[[NSBundle mainBundle] loadNibNamed:@”CCell” owner:self options:nil] objectAtIndex:0];
      Although I am using StoryBoard I created a xib file to define the cell. In the tablevew I don’t define any prototipe cell.
      The cell is never null …
      I hope It is useful for you.

       
      • Freitas

        October 22, 2012 at 3:46 am

        Hello Tim,
        I confess I’m also a bit lost in this situation.

        someone already has a simple solution.

         
  11. Tim Roadley

    March 27, 2012 at 5:41 am

    Seong,

    Sorry I’ve not replied in a while, I have been so busy at work recently I haven’t had time! A quick search led me to this question on stack overflow. Have you tried that?

    When I get a change I will look in to this issue more

    Cheers

     
  12. Seong

    March 27, 2012 at 1:02 pm

    Thank you Tim and santiago.
    As always, Tim saved me.
    //santiago ‘s help was very useful too:)

    Finally it works, maybe.
    using a hint used in this tutorial just what Tim suggested me.

    at cellForRowAtIndexPath,
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    is still doing great.

    After that, like the tutorial, set a property of an NSString *savedSearchTerm. and @synthsize savedSearchTerm;.

    and insert a line at,

    - (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
    {
    [self setSavedSearchTerm:searchText];
    ......

    And the last one thing,

    - (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
    {
    [self setSavedSearchTerm:nil];
    }

    and that’s all.

    Now

    if (savedSearchTerm){

    works, in preparing for Segue.

    the savedSearchTerm does not save any search term though. :)
    it looks not so beautiful, but it works yet :)

    Thank you.

     
    • Zachary Fisher

      May 31, 2012 at 7:47 am

      Seong,
      Thanks for helping figure this out. It has been giving me a headache for days… one question. When I use your code, it only really works if there is a single object in the search results. If, for example, I type in the character “c”, both c++ and chief financial officer are choices. No matter which one I select, it goes to the c++ detail. I am assuming this is because the indexpath.row info is not being passed in the searched table, so it can’t grab the second item. Did you notice this? Any thoughts on how to fix?

      Thanks!
      Zack

       
  13. charlesGray

    April 4, 2012 at 10:21 am

    very helpful tutorials … i like your ‘style’ of instruction … thanks

    this tutorial would be much more helpful IF after the selection process you could actually ‘select’ one of the “cellsForRowAtIndexPath” and seque to the ‘edit’ tableView.

    what’s next?

     
    • Tim Roadley

      April 4, 2012 at 10:29 am

      I understand the issue completely and am working on a solution. I’ll let you know when I have one.

      Cheers

       
      • charlesGray

        April 4, 2012 at 10:51 am

        thanks for your timely reply and understanding ….

        i’ll let you know if i craft a solution, before hearing from you

         
      • Chrys

        May 25, 2012 at 12:53 pm

        Any update on this? Not sure the best way to segue from a searched cell? …

         
      • Chrys

        May 25, 2012 at 1:04 pm

        Never mind, I posted without reading properly – Seong’s solution is awesome

         
  14. charlesGray

    April 4, 2012 at 10:22 am

    follow-up requested

     
    • charlesGray

      April 6, 2012 at 2:20 am

      i have a continuing issue with adding the managedObjectContext obj setup in the appDelegate …

      saw the same compilation error the first time i wrote your tutorial … but it ‘went away’ … unfortunately don’t know what i did to correct the problem

      code is all correct, but i get a “Receiver type … forward declaration” error …

      what causes this? and how do you make it happy ?

       
      • charlesGray

        April 6, 2012 at 3:30 am

        found the answer … F.Y.I.

        by including the Stanford CoreDataTableViewController you get the import of CoreData/CoreData.h … until that point the ‘… forward declaration’ error is stubbornly present

        http://stackoverflow.com/questions/8088030/cannot-create-a-managed-object-context-on-ios

        thanks again for all you’re doing to further the education process for so many!

         
      • Tim Roadley

        April 6, 2012 at 6:35 am

        Great news that you found the fix so quickly, gotta love Stack Overflow :)

         
  15. Tony Short

    May 22, 2012 at 6:26 am

    I have followed this tutorial and have found it most useful. My question to Tim or to his followers is this:

    Rather than fetching from a search bar, could I have a Text Field and a button that allows the user to enter a search criteria followed by clicking submit to search?

    The app that I’m aiming to create will have pre-loaded data and I need a fetch mechanism to allow the user to search on the data.

    Thank You

    Tony

     
  16. zav

    June 23, 2012 at 4:28 am

    Great, but what do we do now if we want to move the search bar out of the tableView and into its own view so it doesn’t scroll away?

    It’s not exactly smooth sailing in the storyboard or in restructuring the code.

    Any advice would be appreciated.

     
    • Jack

      August 11, 2012 at 11:53 pm

      @zav thats what I’d like to know

       
  17. Reno Chong

    June 26, 2012 at 6:31 pm

    Hey, I would like to ask how you configure the cancel function on the search bar but when i look at the Roles.m file, I could not find any cancel method for the search bar. Hope to hear from you soon.

    Thank you.

     
  18. Cuong Duong

    June 29, 2012 at 3:10 pm

    Hi Tim,
    Thank you for your examples.
    I’m a newer for Objective-C programming, your examples is very helpful for me.
    I have a question for part 7, Why I can’t tab on search result for view detail? How do I for view detail when tab on seach result?
    Thanks so much.

     
  19. Cuong Duong

    June 29, 2012 at 3:11 pm

    Hi Tim,
    Thank you for your examples.
    I’m a newer for Objective-C programming, your examples is very helpful for me.
    I have a question for part 7, Why I can’t tab on search result for view detail? How do I for view detail when tab on search result?
    Thanks so much.

     
  20. Johny Mnemonic

    October 4, 2012 at 6:20 am

    Tim, let the power of Universe be with you ! ! !
    Cheers from Ukrainian developer !

     
  21. Leandro Davi de Freitas

    October 22, 2012 at 5:37 am

    Hello Cuong Duong and Seong,

    for those who are still having the problem.
    I got the solution I found was the following:

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
    if (tableView == self.searchDisplayController.searchResultsTableView)
    {

    RoleDetailTVC *nv = [self.storyboard instantiateViewControllerWithIdentifier:@"identifierName"];//click on the storyboard view and put that name as an identifier

    nv.delegate = self;
    nv.managedObjectContext = self.managedObjectContext;
    self.selectedRole = [self.searchResults objectAtIndex:indexPath.row];
    nv.role = self.selectedRole;
    [self.navigationController pushViewController:nv animated:YES];

    }

    }

    I have helped.

     
  22. JP

    January 12, 2013 at 4:20 am

    Hi Leandro,

    That doesn’t seem to work for me. It opens it up but doesn’t display the selected role, also it adds to the stack somehow so when clicking the back button you have to do it twice.

    Hi Tim,
    I’ve downloaded the completed code after section 8, and in the completed code the search doesn’t work either since from a search you can’t actually go to the detail. Has anyone found the suggested solution?

    Thanks

     
  23. Chen Lala

    January 13, 2013 at 11:32 am

    Hi Tim if I use sql to be database , how do I to connect with XXX.jpg file ? My purpose is to write [Hotel App] so I need to show hotel photo. Do you have any suggestion about framework! thanks for your respond.

     

Leave a Reply