1

I have been trying to implement UISearchController by following stackoverflow thread:

How to implement UISearchController with objective c

and Apples's documentation, but can't make it working. When the initial controller (EVTSearchViewController which is defined as UIViewController) that complies with UISearchBarDelegate, UISearchControllerDelegate, UISearchResultsUpdating, UITableViewDelegate (I needed to make it compliant with UITableViewDelegate too, since I am making it a delegate of the other UITableViewController type EVTSearchResultsViewController *resultsControllers tableView) delegates is presented, I see my .xib with the UISearchBar and UITableView, I click the search bar and start typing:

enter image description here

When I type one letter, search bar disappears and nothing is shown:

enter image description here

First, I do not want the search bar to disappear. Second, updateSearchResultsForSearchController seems not to be called at all since NSLog() set up there does not produce any output when I am typing in the search bar.

I have .xib for the EVTSearchViewController and it has a UISearchBar that I am connecting to the respective property: IBOutlet UISearchBar *searchBar which is then made to point to the UISearchControllers' searchBar:

self.searchBar = self.searchController.searchBar

There is also UITableView that I put below the UISearchBar in the .xib. Another controller that I am using inside EVTSearchViewController is EVTSearchResultsViewController, it is UITableViewController and it does not have its .xib.

Below is the code for viewDidLoad and updateSearchResultsForSearchController methods:

- (void)viewDidLoad
{
_resultsController = [[EVTSearchResultsViewController alloc] init];
_searchController = [[UISearchController alloc] initWithSearchResultsController:_resultsController];

self.searchController.searchResultsUpdater = self;
self.searchController.searchBar.placeholder = nil;
[self.searchController.searchBar sizeToFit];
self.searchBar = self.searchController.searchBar;


// we want to be the delegate for our filtered table so didSelectRowAtIndexPath is called for both tables
self.resultsController.tableView.delegate = self;
self.searchController.delegate = self;
self.searchController.dimsBackgroundDuringPresentation = YES; // default is YES
self.searchController.searchBar.delegate = self; // so we can monitor text changes + others

// Search is now just presenting a view controller. As such, normal view controller
// presentation semantics apply. Namely that presentation will walk up the view controller
// hierarchy until it finds the root view controller or one that defines a presentation context.
//
self.definesPresentationContext = YES;  // know where you want UISearchController to be displayed
}

- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {

// update the filtered array based on the search text
NSString *searchText = searchController.searchBar.text;
NSLog(@"searchText: %@", searchText);
if (searchText == nil) {

    // If empty the search results are the same as the original data
    self.searchResults = [[[EVTItemStore sharedStore] allItems] mutableCopy];

} else {

    NSMutableArray *searchResults = [[NSMutableArray alloc] init];

    NSArray *allEvents = [[EVTItemStore sharedStore] allItems];
    NSLog(@"allEvents: %@", allEvents);
    for (EVTItem *event in allEvents) {

        /*if ([event.number containsString:searchText] || [[phoneMO.closrr_id filteredId] containsString:searchText] || [[phoneMO.contact.fullname lowercaseString] containsString:[searchText lowercaseString]]) {
            [searchResults addObject:phoneMO];
        }*/
        if ([event.eventName containsString:searchText]) {
            [searchResults addObject:event];
        }
    }

    self.searchResults = searchResults;

}

// hand over the filtered results to our search results table
EVTSearchResultsViewController *resultsController = (EVTSearchResultsViewController *)self.searchController.searchResultsController;
resultsController.filteredEvents = self.searchResults;
[resultsController.tableView reloadData];
}

The respective @properties defined in EVTSearchViewController:

@interface EVTSearchViewController ()
@property (weak, nonatomic) IBOutlet UISearchBar *searchBar;

@property (nonatomic, strong) UISearchController *searchController;
@property (nonatomic, strong) EVTSearchResultsViewController *resultsController;
@property (nonatomic, strong) NSMutableArray *searchResults;

// For state restoration
@property BOOL searchControllerWasActive;
@property BOOL searchControllerSearchFieldWasFirstResponder;

@end

Then, here is the code for EVTSearchResultsViewController:

#import "EVTSearchResultsViewController.h"

@implementation EVTSearchResultsViewController

- (instancetype)init
{
// Call the superclass's designated initializer
self = [super initWithStyle:UITableViewStylePlain];

if (self) {
}
return self;
}

- (void)viewDidLoad {
[super viewDidLoad];

[self.tableView registerClass:[UITableViewCell class]
       forCellReuseIdentifier:@"UISearchViewCell"];

}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

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

return [self.filteredEvents count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView
     cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier:@"UISearchViewCell"
                                forIndexPath:indexPath];

cell.textLabel.text = self.filteredEvents[indexPath.row];

return cell;
}

@end

The methods of EVTSearchResultsViewController above are not called at all which makes look very strange for me, why do we need it at all, then?

I tried to set UISearchBar the way Apple docs recommend:

self.resultsTableView.tableHeaderView = self.searchController.searchBar;

but it gives me a non-responsive search box, so when pressing nothing happens.

Could anyone, please, help with resolving the question. The other question linked above can also be clarified then. Thank you.

Community
  • 1
  • 1
Nikita Vlasenko
  • 4,004
  • 7
  • 47
  • 87
  • Your implementation seems overly complex. You generally want to add the uisearchbar to the tableview you want to search not spread across multiple classes and xib's. Try starting from scratch with a very simple example until you get the coding pattern down. – beyowulf Nov 25 '15 at 02:58
  • well, I believe that all of the things are required and should be done in this way (or similar way), and it can not be more simple. If you look at apple docs, it is already pretty complex: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UISearchController/ – Nikita Vlasenko Nov 25 '15 at 04:04
  • The methods are called right now, so I see workflow better. I have two main problems right now: the one that images represent above when one view runs over another; the other is that `NSString *searchText = searchController.searchBar.text` in `updateSearchResultsForSearchController` is somehow not set when I type text. – Nikita Vlasenko Nov 25 '15 at 04:15
  • Main problem that I have right now is the one shown above when one view runs over another. I solved the issue of setting the text (I just see right NSLog() outputs) – Nikita Vlasenko Nov 25 '15 at 04:45
  • 1
    You should use this line from the documentation: self.tableView.tableHeaderView = self.searchController.searchBar; As I suggested in my first comment, see the link you sent me for more details. – beyowulf Nov 25 '15 at 05:00
  • With the approach I am using above, things are working perfect in console (unfortunately, not showing up correctly). Apple's approach leads to completely unresponsive `UISearchBar`, do you know why it might happen? There are no errors in the console, it just does not respond. – Nikita Vlasenko Nov 25 '15 at 05:15
  • Did you delete the search bar you dragged out in IB? – beyowulf Nov 25 '15 at 11:41
  • @beyowulf It is fine, I made it working, so the problem is solved. Your advice was right, I should have used apple's suggestion. The unresponsiveness was related to delegates issues / .xib files for sure. – Nikita Vlasenko Nov 25 '15 at 20:12
  • Will post the answer soon. – Nikita Vlasenko Nov 25 '15 at 20:13
  • Glad you got it working. – beyowulf Nov 25 '15 at 20:25

1 Answers1

1

So, I solved the issue and implemented basic search with UISearchController. Here is what we need to do to implement the basic search:

  1. Create two UITableViewControllers classes with NO .xib files. Yes, there should be no .xib files and we are just creating two classes. In the code below their names are EVTSearchViewController and EVTSearchResultsViewController.
  2. Make one of the controllers to comply with the delegates: <UISearchBarDelegate, UISearchControllerDelegate, UISearchResultsUpdating>

Here is the code for the header file of EVTSearchViewController:

#import <UIKit/UIKit.h>

@interface EVTSearchViewController : UITableViewController <UISearchBarDelegate, UISearchControllerDelegate, UISearchResultsUpdating>

@end

Here is the EVTSearchResultsViewController's header:

#import <UIKit/UIKit.h>

@interface EVTSearchResultsViewController : UITableViewController

@property (nonatomic, strong) NSMutableArray *filteredEvents;

@end

The NSMutableArray *filteredEvents will hold the results of the search. We should NOT implement any of the UITableViewController delegate methods in EVTSearchViewController.m, but should in EVTSearchResultsViewController.m.

Here is the top part of EVTSearchViewController:

#import "EVTSearchViewController.h"
#import "EVTSearchResultsViewController.h"

// Importing the class that stores `EVTItem` object classes
#import "EVTItemStore.h"
#import "EVTItem.h"

@interface EVTSearchViewController ()
@property (nonatomic, strong) UISearchController *searchController;
// We created this class
@property (nonatomic, strong) EVTSearchResultsViewController *resultsController;
// array to hold the results of the search
@property (nonatomic, strong) NSMutableArray *searchResults;

@end

Here is the code for EVTSearchViewController's viewDidLoad: method:

- (void)viewDidLoad
{
[super viewDidLoad];
self.resultsController = [[EVTSearchResultsViewController alloc] init];
self.searchController = [[UISearchController alloc] initWithSearchResultsController:self.resultsController];

self.searchController.searchResultsUpdater = self;
self.searchController.searchBar.placeholder = nil;
[self.searchController.searchBar sizeToFit];
// This line of code is very important. Here we are using apple docs'
// suggestion. UITableViewController has tableView property, so
// we are just setting tableView`s header to apples' UISearchController`s' `searchBar property
self.tableView.tableHeaderView = self.searchController.searchBar;

self.searchController.delegate = self;

// default is YES
self.searchController.dimsBackgroundDuringPresentation = YES;

// so we can monitor text changes + other changes
self.searchController.searchBar.delegate = self;

// know where you want UISearchController to be displayed
self.definesPresentationContext = YES;
}

Then we add to EVTSearchViewController the following method:

- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {

// update filtered array based on the search text
NSString *searchText = searchController.searchBar.text;
if (searchText == nil) {

    // If empty the search results should be the same as the original data
    self.searchResults = [[[EVTItemStore sharedStore] allItems] mutableCopy];

} else {

    NSMutableArray *searchResults = [[NSMutableArray alloc] init];

// [[EVTItemStore sharedStore] allItems] message retrieves
// all of the objects that I have in datastore EVTItemStore
    NSArray *allEvents = [[EVTItemStore sharedStore] allItems];

// EVTItem class has a property eventName which we are using
// for searching, then adding it to our searchResults array
    for (EVTItem *event in allEvents) {
        if ([event.eventName containsString:searchText]) {
            [searchResults addObject:event];
        }
    }

    self.searchResults = searchResults;
}

// hand over the filtered results to our search results table
EVTSearchResultsViewController *resultsController = (EVTSearchResultsViewController *)self.searchController.searchResultsController;
resultsController.filteredEvents = self.searchResults;
[resultsController.tableView reloadData];
}

Another controller's EVTSearchResultsViewController @implementation part looks like this:

@implementation EVTSearchResultsViewController

- (instancetype)init
{
// Call the superclass's designated initializer
self = [super initWithStyle:UITableViewStylePlain];

if (self) {
}
return self;
}

- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section {
return [self.filteredEvents count];
}


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

UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier:@"UISearchViewCell"
                                forIndexPath:indexPath];
EVTItem *event = self.filteredEvents[indexPath.row];
cell.textLabel.text = event.eventName;
return cell;
}

- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class]
                         forCellReuseIdentifier:@"UISearchViewCell"];
}

@end

That's it. If we need to further customize our cells, we should be able to do it by making UISearchViewCell.xib for EVTSearchResultsViewController, and using the following viewDidLoad instead:

- (void)viewDidLoad
{
[super viewDidLoad];

// Load the NIB file
UINib *nib = [UINib nibWithNibName:@"UISearchViewCell" bundle:nil];

// Register this NIB which contains the cell
[self.tableView registerNib:nib forCellReuseIdentifier:@"UISearchViewCell"];
}
Nikita Vlasenko
  • 4,004
  • 7
  • 47
  • 87