Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UISearchController: show results even when search bar is empty

You can simply implement the UISearchResultsUpdating protocol and set the results controller view to always show in updateSearchResultsForSearchController:

 func updateSearchResultsForSearchController(searchController: UISearchController) {

   // Always show the search result controller
   searchController.searchResultsController?.view.hidden = false

   // Update your search results data and reload data
   ..
}

This works because the method is called even when the search bar is activated, without any text.


If your searchBar is active but has no text, the underlying tableView results are shown. That's the built-in behavior, and the reason why searchResultsController is hidden for that state.

To change the behavior when search is active but not filtering, you're going to have to show the searchResultsController when it is normally still hidden.

There may be a good way to accomplish this via <UISearchResultsUpdating> and updateSearchResultsForSearchController:. If you can solve it via the protocol, that's the preferred way to go.

If that doesn't help, you're left with hacking the built-in behavior. I wouldn't recommend or rely on it, and it's going to be fragile, but here's an answer if you choose that option:

  1. Make sure your tableViewController conforms to <UISearchControllerDelegate>, and add

    self.searchController.delegate = self;

  2. Implement willPresentSearchController:

    - (void)willPresentSearchController:(UISearchController *)searchController
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            searchController.searchResultsController.view.hidden = NO;
        });
    }
    

    This makes the searchResultsController visible after its UISearchController set it to hidden.

  3. Implement didPresentSearchController:

    - (void)didPresentSearchController:(UISearchController *)searchController
    {
        searchController.searchResultsController.view.hidden = NO;
    }
    

For a better way to work around the built-in behavior, see malhal's answer.


Updated for iOS 13

From iOS13, we got system API support for this behaviour. You can set the property showsSearchResultsController = true

WWDC screenshot

For iOS 12 and below

I am recently working on UISearchController. I want to show search history in searchResultsController when search bar is empty. So searchResultsController needs to show up whenever UISearchController gets presented.

Here, I use another solution to make the searchResultsController always visible by overriding the hidden property in a custom view.

for example, my searchResultsController is a UITableViewController. I create a VisibleTableView as a subclass of UITableView, and then change the UITableView custom class of searchResultsController to VisibleTableView in xib or storyboard. This way, my searchResultsController will never be hidden by UISearchController.

The good things here:

  1. Easier to implement than KVO.

  2. No delay to show searchResultsController. Flipping the hidden flag in "updateSearchResults" delegate method works, but there is a delay to show the searchResultsController.

  3. It does't reset the hidden flag, so there is no UI gap/jumping between hidden and visible.

Swift 3 sample code:

class VisibleTableView: UITableView {
override var isHidden: Bool {
    get {
        return false
    }
    set {
        // ignoring any settings
    }
}
}

I have tried PetahChristian solution, the preload result did show up when we first focus the searchbar, but when we enter something then clear it, the preload results will not reappear.

I came up with another solution. We only need to add a delegate into SearchResultsController and call it when our searchController.searchBar.text is empty. Something like this:

SearchResultsController:

protocol SearchResultsViewControllerDelegate {
   func reassureShowingList() -> Void
}

class FullSearchResultsViewController: UIViewController, UISearchResultsUpdating{
   var delegate: SearchResultsViewControllerDelegate?
   ...
   func updateSearchResultsForSearchController(searchController: UISearchController) {
    let query = searchController.searchBar.text?.trim()
    if query == nil || query!.isEmpty {
        ...
        self.delegate?.reassureShowingList()
        ...
    }
    ...
}

And in the controller contains the SearchController, we add our delegate:

self.searchResultsController.delegate = self
func reassureShowingList() {
    searchController.searchResultsController!.view.hidden = false
}

With tricky things like this I recommend the sledge hammer approach! That is to detect when something tries to make it hidden and when it does, change it back. This can be done via KVO (Key Value Observing). This will work no matter what, without having to handle all the intricacies of the search bar. Sorry the code is complicated but KVO is an older style API but my code follows recommend practice. In your SearchResultsViewController put this:

static int kHidden;

@implementation SearchResultsViewController

-(void)viewDidLoad{
    [super viewDidLoad];
    [self.view addObserver:self
                   forKeyPath:@"hidden"
                      options:(NSKeyValueObservingOptionNew |
                               NSKeyValueObservingOptionOld)
                      context:&kHidden];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    // if it was our observation
    if(context == &kHidden){
        // if the view is hidden then make it visible.
        if([[change objectForKey:NSKeyValueChangeNewKey] boolValue]){
            self.view.hidden = NO;
        }
    }
    else{
        // if necessary, pass the method up the subclass hierarchy.
        if([super respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]){
            [super observeValueForKeyPath:keyPath
                                 ofObject:object
                                   change:change
                                  context:context];
        }
    }
}

- (void)dealloc
{
    [self.view removeObserver:self forKeyPath:@"hidden"];
}

// Here have the rest of your code for the search results table.

@end

This works in all cases including if the text is cleared.

Lastly, to prevent the table doing an ugly fade to grey then to white when the search activates, use this:

self.searchController.dimsBackgroundDuringPresentation = NO;

Swift 3 Version:

If your searchResultController is not nil and you are using a separate table view controller to show the search results, then you can make that table view controller conform to UISearchResultUpdating and in the updateSearchResults function, you can simply unhide the view.

func updateSearchResults(for searchController: UISearchController) {
    view.hidden = false
}

Swift 4 Version:

func updateSearchResults(for searchController: UISearchController) {
    view.isHidden = false
}