UISearchDisplayController - how to preload searchResultTableView
Asked Answered
C

8

27

I want to show some default content when the user taps the Searchbar, but before any text is entered.

I have a solution working using settext:

- (void) searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {

   [searchDisplayController.searchBar setText:@" "];

}

This works but it's not elegant and the hint text in the Searchbar disappears.

Is there a better way to preload data to the SearchResultTableView in a UISearchDisplayController, without having to implement the whole UISearch functionality yourself in a custom controller?

For a demo of the desired effect, look at Safari's search box, if you tap it, the search interface opens with previous searches showing.

Carburetor answered 3/6, 2011 at 9:14 Comment(2)
3 liner! Of course it was about 40 lines and a lot of reading until I figured out what was going on with that UISearchDisplayController. Good question, I love little challenges like this to learn iOS.Spruce
See this link https://mcmap.net/q/535086/-show-uisearchdisplaytableview-when-search-string-is-empty-duplicate This is a solution with carriage return.Olfe
S
9

OK, I have it. Preload your dataSource with some data and do the following hack (nothing illegal) and you'll get the tableView to show up. Note that there may be some clean-up to do, but this will get your default data to display.

In viewDidLoad of the view controller than owns the UISearchBar:

[super viewDidLoad];
[self.searchDisplayController.searchResultsTableView reloadData];
// whatever you named your search bar - mine is property named searchBar
CGRect testFrame = CGRectMake(0, 20, self.searchBar.frame.size.width, 100);
self.searchDisplayController.searchResultsTableView.frame = testFrame;
[self.searchBar.superview addSubview:self.searchDisplayController.searchResultsTableView];

Here's what's happening:
The UISearchBar doesn't want to show the searchResultsTableView until you start editing. If you touch the tableView (e.g. reloadData) it will load and be there, sitting in memory with frame = CGRectZero. You give it a proper frame and then add it to the superview of the searchBar, et voila.

I verified this is correct by displaying the superview of the searchBar and the superview of the tableView after a proper load of the tableView - they have the same superview. So, I go back and add the tableView to the superview early, and sure enough it shows up. For me, it showed up dimmed (maybe another view above it? alpha set lower?), but was still clickable. I didn't go any further, but that definitely gets you your tableView displaying without any user interaction.

Enjoy,

Damien

Spruce answered 1/3, 2012 at 18:32 Comment(3)
This didn't work for me. I already have my searchbar placed well through storyboard. I dont want to position it once again. When I tap on the searchbar, the data isn't loaded.Touchstone
This was not a simple problem - if you read through the "Here's what's happening" you'll see why adding the subview is necessary for it to work. I also haven't verified behavior in ios6 or 7 - there may be a better way to do this now. Good luck!Spruce
In iOS 8 and after you need to do self.view.superview addSubview ... instead of using the searchbar. Then you need to adjust the size of the tabview with the status bar like this: CGRect testFrame = CGRectMake(0, self.notesSearchBar.frame.size.height+ [UIApplication sharedApplication].statusBarFrame.size.height, self.notesSearchBar.frame.size.width, self.view.frame.size.height - self.notesSearchBar.frame.size.height);Touchstone
A
8

I found a much better solution to this issue, and it seems to work perfectly on iOS 6 and 7. While it is still a hack, its a much cleaner and future proof hack than the above. The other solutions do not work consistently and prevent some UISearchDisplayDelegate methods from ever firing! Further I had complex insetting issues which I could not resolve with the above methods. The main issue with the other solutions is that they seriously confuse the internals of the UISearchDisplayController. My solution is based on the observation that UISearchDisplayContoller is a UISearchbarDelegate and that the automatic undimming & showing of results table can be triggered by simulating a keypress in the search field! So:

- (void) searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller 
{
    if ([controller respondsToSelector: @selector(searchBar:textDidChange:)])
        [(id<UISearchBarDelegate>)controller searchBar: controller.searchBar textDidChange: @" "];
}

This code is future proof against crashing by checking it responds to the UISearchbarDelegate method, and sends space @" " to trick the UISearchDisplayController into thinking user has typed a letter.

Now if the user types something and then erases it, the table will dim again. The other solutions try to work around this by doing something in the searchDisplayController:didHideSearchResultsTableView: method. But this doesn't make sense to me, as surely when you cancel the search it will need to truly hide your results table and you may need to run code in this case. My solution for this part is to subclass (note you could probably use a Method Swizzled Category to make it work everywhere if needed in your project):

// privately declare protocol to suppress compiler warning
@interface UISearchDisplayController (Super) <UISearchBarDelegate>
@end

// subclass to change behavior
@interface GMSearchDisplayController : UISearchDisplayController
@end

@implementation GMSearchDisplayController

- (void) searchBar: (UISearchBar *) searchBar textDidChange: (NSString *) searchString
{
    if (searchString.length == 0)
        searchString = @" ";
    if ([super respondsToSelector: @selector(searchBar:textDidChange:)])
        [super searchBar: searchBar textDidChange: searchString];
}

@end

This code works by intercepting the textDidChange delegate method and changing nil or empty strings in to space string @" " preventing the normal hiding/dimming that occurs on an empty search bar. If you are using this second bit of code, then you could modify the first bit to pass a nil instead of @" " as this second bit will do the needed conversion to @" " for you.

In my own project, I needed to handle the case that user does type a space, so instead of @" " above I used a defined token:

// arbitrary token used internally
#define SEARCH_PRELOAD_CONDITIONAL @"_#preresults#_"

And then handle it internally by converting it back to nil string:

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
    if ([searchString isEqualToString: SEARCH_PRELOAD_CONDITIONAL])
        searchString = nil;
}

Enjoy! :)

Anatropous answered 17/7, 2014 at 12:4 Comment(0)
S
4

Working solution for iOS 7:

// When the tableView is hidden, put it back
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
    [self.searchDisplayController.searchResultsTableView reloadData];
    controller.searchResultsTableView.hidden = NO;

    // Also, remove the dark overlay
    for (UIView *v in [[controller.searchResultsTableView superview] subviews]) {
        // This is somewhat hacky..
        if (v.alpha < 1) {
            [v setHidden:YES];
        }
    }
}

-(void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView {
    [self.searchDisplayController.searchResultsTableView reloadData];
    if (self.searchDisplayController.active == YES) {
        tableView.hidden = NO;
    }
}
Smalto answered 13/5, 2014 at 13:51 Comment(1)
This one shows a second lag and I can see the overlay view coming up and going away. Anything to avoid that??Blubber
T
1

I have the solution. Insert these three methods.You have to have the tableview preloaded with the data in order this to work. I have this codes working in my code so it has to work for you as long as you have already preloaded the data and you are using search display controller.

- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
    CGRect testFrame = CGRectMake(0, self.notesSearchBar.frame.size.height, self.notesSearchBar.frame.size.width, self.view.frame.size.height - self.notesSearchBar.frame.size.height);
    self.searchDisplayController.searchResultsTableView.frame = testFrame;
    [self.notesSearchBar.superview addSubview:self.searchDisplayController.searchResultsTableView];

//    [self.view addSubview:self.searchDisplayController.searchResultsTableView];
    controller.searchResultsTableView.hidden = NO;
}

-(void) searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
    CGRect testFrame = CGRectMake(0, self.notesSearchBar.frame.size.height, self.notesSearchBar.frame.size.width, self.view.frame.size.height - self.notesSearchBar.frame.size.height);
    self.searchDisplayController.searchResultsTableView.frame = testFrame;
    [self.notesSearchBar.superview addSubview:self.searchDisplayController.searchResultsTableView];

    //    [self.view addSubview:self.searchDisplayController.searchResultsTableView];
    controller.searchResultsTableView.hidden = NO;
}


-(void) searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
    controller.searchResultsTableView.hidden = YES;
}
Touchstone answered 30/10, 2013 at 2:47 Comment(1)
This is a good solution. Also maybe there should also be an [self.searchDisplayController.searchResultsTableView removeFromSuperview]; in searchDisplayControllerWillEndSearchKatowice
S
1

This works in iOS 8:

- (void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
  self.searchDisplayController.searchResultsTableView.hidden = NO;
}

- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
    self.searchDisplayController.searchResultsTableView.hidden = NO;
    [self.searchDisplayController.searchResultsTableView.superview.superview bringSubviewToFront:self.searchDisplayController.searchResultsTableView.superview];

    CGRect frame = self.searchDisplayController.searchResultsTableView.frame;
    self.searchDisplayController.searchResultsTableView.frame = CGRectMake(frame.origin.x, 64, frame.size.width, frame.size.height);
}

However, I did not like this hack and replaced the searchDisplayController by my own implementation (UISearchBar with UITableView).

Swenson answered 7/1, 2015 at 1:19 Comment(0)
F
1

I tested the following in iOS 9 with UISearchController.

By default, whenever there is no text in the UISearchBar, the table shown will be your original table view (referred to as originalTableView from here on out). This may or may not have a black see-through layer depending on the value of dimsBackgroundDuringPresentation.

Once the user enters any text into the UISearchBar, the contents of the new table view will be shown (I will refer to this as the searchTableView).

Note that in both of these solutions, I'm implementing the UISearchController in a manner similar to how Apple does so in this example; namely, there are two separate UITableViewController's in charge of showing the data.

Solution 1: Complicated implementation, smoother animation

For a smoother animation:

  1. Implement the UISearchControllerDelegate's willPresentSearchController and willDismissSearchController methods.
  2. In willPresent, update your originalTableView's data to get ready to display whatever zero text data you want, then call reloadData().
  3. In willDismiss, revert the process to show the original contents and call reloadData().

Example:

// here tableView refers to originalTableView

extension ViewController: UISearchControllerDelegate {
    func willPresentSearchController(searchController: UISearchController) {
        data = ["A", "B", "C"]
        tableView.reloadData()
    }

    func willDismissSearchController(searchController: UISearchController) {
        data = ["a", "b", "c"]
        tableView.reloadData()
    }
}

In this fictional example, the data is displayed as a,b,c when the user is not focused on the UISearchBar, but is displayed as A,B,C once the user taps on the UISearchBar.

Solution 2: Quick to implement, choppy animation

Instead of implementing the logic for the search controller in both originalTableView and searchTableView, you can use this solution to simply show the searchTableView even though it is hidden by default.

Most likely, you are already implementing the UISearchResultsUpdating protocol. By simply calling tableView.hidden = false in that method, you should get the behavior you are after; albeit with a less than stellar animation.

// here tableView refers to searchTableView

extension SearchViewController: UISearchResultsUpdating {
    func updateSearchResultsForSearchController(searchController: UISearchController) {
        tableView.hidden = false
        filterData(text: searchController.searchBar.text!)
        tableView.reloadData()
    }
}
Footstool answered 6/7, 2015 at 5:59 Comment(0)
C
-1

Here's a way to pre-populate the searchController.

// 1. Save previous search results as soon as user enters text

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

    return YES;
}

// 2. load previous search string at startup

- (void)viewDidLoad 
{
    // create data source for table view
    // searchData may be a NSMutableArray property defined in your interface

    self.searchData = // search your master list for searchString

    // or create your own list using any search criteria you want

    // or you may act depending on user preference
    if(!showPreviousSearch)
    {
        // [searchData removeAllObjects];
    }
}

// 3. provide correct data source when asked

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) 
    {
        ...
    }

    NSMutableArray *dataSource = nil;
    if (tableView == [[self searchDisplayController] searchResultsTableView])
    {
        dataSource = self.searchData;
    }
    else
    {
        dataSource = self.masterData;
    }

    ...

    return cell;
}

// Hope this helps.

Thanks

Cartography answered 3/6, 2011 at 9:14 Comment(0)
P
-1

Why don't you have a look at the TableSearch project that comes in Xcode? It more or less address what you need. It shows a table right at the beginning and shades it out when search field is tapped. Check TableSearch in Documentation and API reference in Xcode 4 under help.

Also take a look at this It almost does what you need with a UISearchBar and a UITableView. I'd prefer that approach instead. Tweak it.

And to address your needs further, I'd suggest using two datasources. One holding all the contents (this is what you would show at the very beginning) and one to hold the filtered search results. Now go ahead and empty the first datasource and fill it up with the contents of the second one to store & dim-show the results of the previous search. Proceed this way. Use this approach in the TableSearch code for your task.

Pulsimeter answered 28/9, 2011 at 12:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.