UISearchDisplayController with no results tableView?
Asked Answered
P

10

41

Usually, a UISearchDisplayController, when activated, dims the tableView and focuses the searchBar. As soon as you enter text into the searchBar, it creates a searchResultsTableView that displays between the searchBar and the keyboard. The searchDisplayController's delegate gets called when this second UITableView is loaded/shown/hidden/unloaded. Usually it shows live search results or autocompletion entries while typing.

In my app, I want to search a webservice and I don't want to call the webservice for each letter the user enters. Therefore, I want to entirely disable the searchResultsTableView and keep the dimmed black overlay while he enters text. I would then trigger the search (with a loading screen) once he hits the search button.

Just returning zero rows for the searchResultsTableView doesn't look nice since it displays an empty searchResultsTableView with a "no results" message. I tried to hide the table when it appears (searchDisplayController:didLoadSearchResultsTableView:) which works, but the blacked dimmed overlay is also hidden so that the underlying tableView is completely visible again.

Any ideas besides recreating the UISearchDisplayController functionality from scratch?

Pear answered 31/7, 2009 at 18:44 Comment(0)
C
38

here is a little trick that i just figured out and also you have to return 0 results while editing searchstring

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
    savedSearchTerm = searchString;

    [controller.searchResultsTableView setBackgroundColor:[UIColor colorWithWhite:0.0 alpha:0.8]];
    [controller.searchResultsTableView setRowHeight:800];
    [controller.searchResultsTableView setScrollEnabled:NO];
    return NO;
}

- (void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
    // undo the changes above to prevent artefacts reported below by mclin
}

i think you'll figure out what to do next

Cable answered 2/10, 2009 at 0:59 Comment(6)
Works the first time for me, but if you search, click the x to clear and then enter more text, it shows all the rows with opaque black background and white dividing lines. I guessed it might be the the UITableCellViews, so I added this to you function and it worked: for (UIView *subview in self.searchDisplayController.searchResultsTableView.subviews) { [subview removeFromSuperview]; }Rocca
Also worth putting 'controller.searchResultsTableView.scrollEnabled = NO;' in there tooMorissa
Brilliant solution. And +1 to @Morissa too. Why Apple don't have this as a setting you can choose seems really stupid, considering it's exactly how the Maps app works.Emergent
Few other points to keep in mind: (1) tap on this table wouldn't result in UISearchDisplayController being dismissed (I had to use UITapGestureRecognizer). (2) on iPad background color alpha should be ~0.5Dysprosium
I tried this and ran into a few things. I generally find Barry's approach more elegant since it uses the existing results table view.Droplet
Using alpha 0.4 makes the tint look exactly the same on iOS7.Ribaldry
N
19

Have you tried this:

[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_lookup:) object:nil];
[self performSelector:@selector(_lookup:) withObject:txt afterDelay:0.20];

This way, if the user types another char within 1/5sec, you only make one web call.

Nariko answered 10/9, 2009 at 16:51 Comment(1)
With the object:nil, will the cancel call actually match? You're probably better off calling plain old [NSObject cancelPreviousPerformRequestsWithTarget:self] if you haven't got any other perform requests going on.Northeaster
K
9

Nothing of the above seemed to work well in the end, so I came up with the following (you have to call removeTableHeader when you are ready to display your results):

- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
    [self setTableHeader];
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    [self setTableHeader];
}

- (void)setTableHeader {
    UIView *headerView = [[UIView alloc] initWithFrame:self.searchDisplayController.searchResultsTableView.frame];
    headerView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.8];

    [self.searchDisplayController.searchResultsTableView setBackgroundColor:[UIColor clearColor]];
    [self.searchDisplayController.searchResultsTableView setScrollEnabled:NO];
    [self.searchDisplayController.searchResultsTableView setTableHeaderView:headerView];

    [headerView release];
}

- (void)removeTableHeader {
    [self.searchDisplayController.searchResultsTableView setBackgroundColor:[UIColor whiteColor]];
    [self.searchDisplayController.searchResultsTableView setScrollEnabled:YES];
    [self.searchDisplayController.searchResultsTableView setTableHeaderView:nil];
}

Obviously, it make the table transparent, adds a black/translucent table header with the same size as the table, and disables scrolling on the table so you cannot get above or past the header. As a bonus, you could add something to the header view ('please wait...' or an activity indicator).

Kareykari answered 3/9, 2011 at 17:0 Comment(4)
This sorta works, but you can't click on the header to dismissFolly
This solution is the cleanest and most effective I found. The main problem with other solutions here is that when the 'fake' shadow overlay is visible and you dismiss the search interface, iOS forces the tableView to become opaque and the screen quickly flashes white, which is ugly. That doesn't happen with this implementation. @chaiwalla, if you want to mimic default tap-to-dismiss behavior just add a UITapGestureRecognizer to the headerView and call setActive:animated: on searchDisplayController in the callback.Nemhauser
Just a style-related remark. You might want to use showTableHeader instead of setTableHeader since it's not a real setter in this case.Droplet
+1 This is IMHO the most elegant solution. To fix @chaiwalla's remark I used a UIButton within the HeaderView and then call [searchDisplayController setActive:NO] for the IBAction, thus closing the search when touching this area.Droplet
C
9

Had the same problem as you, I handled it by a) setting the alpha of the searchResultsTableView to 0 when beginning searching, and then by b) adding/removing the overlayView to the viewController's view. Works like a charm for me.

@interface MyViewController()
//...
@property(nonatomic, retain) UIView *overlayView;
//...
@end

@implementation MyViewController
@synthesize overlayView = _overlayView;

//...

- (void)viewDidLoad
{
    //...

    //define your overlayView
    _overlayView = [[UIView alloc] initWithFrame:CGRectMake(0, 44, 320, 480)];
    _overlayView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.8];
}

//hide the searchResultsTableView
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller
{
    self.searchDisplayController.searchResultsTableView.alpha = 0.0f;
}

//when ending the search, hide the overlayView
- (void) searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
    [_overlayView removeFromSuperview];
}

//depending on what the user has inputed, add or remove the overlayView to the view of the current viewController 
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{   

    if ([searchString length]>0) 
    {
        [self.view addSubview:_overlayView];
    }
    else
    {
        [_overlayView removeFromSuperview];
    }

    return NO;
}

@end
Cottar answered 30/1, 2012 at 15:28 Comment(1)
I like this solution better than the accepted answer; thanks! Much cleanerPremillennialism
T
2

it should be sufficient to implement the following method in your UISearchDisplayDelegate (which usually is your custom UITableViewController subclass)

- (BOOL) searchDisplayController: (UISearchDisplayController *) controller shouldReloadTableForSearchString: (NSString *) searchString
{
    [self startMyCustomWebserviceSearchAsBackgroundProcessForString: searchString]; //starts new NSThread
    return NO; 
}

have you tried this?

Thumbtack answered 1/8, 2009 at 13:25 Comment(3)
This prevents the searchDisplayController from sending [searchResultsTableView reload], but unfortunately the searchResultsTableView is still displayed ("no results" message)Pear
in this case i am afraid, that the default behaviour of the UISearchDisplayController can't be modified... sorryThumbtack
By pure chance I noticed yesterday, that the YouTube app does it exactly like I am looking for. Unfortunately I didn't find any nice way yet (besides creating UISearchDisplayController).Pear
F
2

Based on user182820's code below is my version. I hide the UISearchDisplayController's table view. When a character is entered in the search box I place a 'dimmed view' so it looks like UISearchDisplayController's 'dimmed view' never went away and then remove it when the search is finished. If you enter some characters and press cancel, the table view briefly goes all white and I don't know how to get around this.

- (void)viewDidLoad {
    ...
    tableViewMask=[UIView new];
    tableViewMask.backgroundColor = [UIColor blackColor];
    tableViewMask.alpha = 0.8;
}

- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller{
    tableViewMask.frame=CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y+controller.searchBar.frame.size.height, self.tableView.frame.size.width, self.tableView.frame.size.height-controller.searchBar.frame.size.height);
    controller.searchResultsTableView.hidden=YES;
}

- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller{
    [tableViewMask removeFromSuperview];
}

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString{
    if (searchString.length==0)
        [tableViewMask removeFromSuperview];
    else 
        [self.tableView addSubview:tableViewMask];
    [searchText autorelease];
    searchText=[searchString retain];
    return NO;
}   
Feeney answered 28/3, 2010 at 19:54 Comment(0)
G
2

What about just doing it as simple as this:

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
self.searchDisplayController.searchResultsTableView.hidden=YES;
return YES;
}

Works fine for me..

Getty answered 4/11, 2012 at 20:37 Comment(1)
This does only partially work, in my case the background is dimmed, and as soon as I start typing the dimmed background is removed and revealing the underlying tableviewDroplet
C
1

I foud a better way, since there is a bug with the "best answer" - the separator and the "No Results" will be shown when scrolling the black background table.

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {

    controller.searchResultsTableView.backgroundColor = [UIColor blackColor];
    controller.searchResultsTableView.alpha = 0.8;
    controller.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleNone;

    for(UIView *subview in tableView.subviews) {
        if([subview isKindOfClass:UILabel.class]) {
            subview.hidden = YES;
        }
    }

    return NO;
}

- (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar {

    self.searchDisplayController.searchResultsTableView.backgroundColor = [UIColor whiteColor];
    self.searchDisplayController.searchResultsTableView.alpha = 1;
    self.searchDisplayController.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;

    for(UIView *subview in tableView.subviews) {
        if([subview isKindOfClass:UILabel.class]) {
            subview.hidden = NO;
        }
    }

    // search results and reload data ....
}
Cupellation answered 17/8, 2010 at 8:49 Comment(1)
You can disable the scroll and get around that 'bug' you mention with this: controller.searchResultsTableView.scrollEnabled = NO;Morissa
R
1

All of the existing answers are overly complicated. You can get away by just hiding the results table view immediately.

-(void)searchDisplayController:(UISearchDisplayController *)controller didShowSearchResultsTableView:(UITableView *)tableView
{
    tableView.hidden = YES;
}
Reduce answered 24/6, 2014 at 17:15 Comment(0)
T
0

I think I found a better implementation for this problem. All the previous answers correctly show a dimmed view identical to what the UITableView looks like before a search, but each solution lacks the functionality to tap the area in order to cancel the search.

For that reason I think this code works better.

First of all, create a BOOL such as searchButtonTapped to indicate whether the search button was tapped. By default it is NO.

Then:

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
if (!searchButtonTapped) {        
    // To prevent results from being shown and to show an identical look to how the tableview looks before a search
    [controller.searchResultsTableView setBackgroundColor:[UIColor clearColor]];
    [controller.searchResultsTableView setRowHeight:160];
    self.searchDisplayController.searchResultsTableView.scrollEnabled = NO;
} else {
    // Restore original settings
    [controller.searchResultsTableView setBackgroundColor:[UIColor whiteColor]];
    [controller.searchResultsTableView setRowHeight:44];
    self.searchDisplayController.searchResultsTableView.scrollEnabled = YES;
}

return YES;
}

This should be clear now based on the other answers. Make sure to also restore the original settings when the user taps on the Search button.

Furthermore, in the cellForIndexPath method add:

cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.contentView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.8];

In order to create the same dimmed view that is shown before text is entered. Make sure you apply these properties to the right cell, i.e., check which UITableView is active and that the user has not tapped the Search button.

Then, crucially, in didSelectRowAtIndexPath:

if (tableView == self.searchDisplayController.searchResultsTableView) {
    if (searchButtonTapped) {
           // Code for when the user select a row after actually having performed a search
    {
else 
    [self.searchDisplayController setActive:NO animated:YES];

Now the user can tap the dimmed area, which will not result in a visible selection of a UITableViewCell, but instead cancels the search.

Triangle answered 1/11, 2011 at 20:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.