25

I have a tableview for showing a list of devices in my application. When viewWillAppear is called, I add the self.searchDisplayController.searchBar as a subview to a headerView. I then assign self.tableView.tableHeaderView = headerView. It looks like this: enter image description here

I scroll the tableview down so that headerview goes out of view and then go to some other view controller by tapping on a cell. When I come back to this tableView, scroll up to the headerView, the searchBar becomes invisible, however on tapping the invisible area the searchDisplayController gets activated and the cancel button doesn't work. This happens for iOS 7 only. Why is this happening? enter image description here
Note: It happens only if the headerView is out of the view when I come back to the tableViewController.

Jageen
  • 6,345
  • 2
  • 37
  • 56
tech savvy
  • 1,417
  • 4
  • 21
  • 42

9 Answers9

18

I've just had the same issue. When I go to debug into the delegate method of UISearchDisplayController at the end search state, the searchBar becomes a subview of an UIView, not the UITableView. Please see below code:

- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller
{
    //My Solution: remove the searchBar away from current super view,
    //then add it as subview again to the tableView
    UISearchBar *searchBar = controller.searchBar;
    UIView *superView = searchBar.superview;
    if (![superView isKindOfClass:[UITableView class]]) {
        NSLog(@"Error here");
        [searchBar removeFromSuperview];
        [self.tableView addSubview:searchBar];
    }
    NSLog(@"%@", NSStringFromClass([superView class]));
}

My solution is remove the searchBar away from current super view, then add it as subview again to the tableView. I've already tested successfully. Hope that help!

Regards

Phien Tram
  • 381
  • 4
  • 8
  • This is nice, you spotted the problem! But there's also one caveat about your solution: when removing the searchBar from superview and adding it to the tableview, the input focus gets lost! – Tyrael Tong Nov 21 '14 at 18:00
5

I have the exact same problem. the search bar is still there and can receive touch events. it is however not rendered. I believe the problem is in UISearchDisplaycontroller because it renders fine if I don't use UISearchDisplayController. I ended up writing a custom SearchDisplaycontroller to replace it. it is very basic and only does what I need.

use it is the same way as you would the normal UISearchDisplayController but self.searchDisplayController will not return anything. you will have to use another pointer to refer to the custom search display controller.

looks like a big ugly work around, but the only one that worked for me. keen to hear of alternatives.

@protocol SearchDisplayDelegate;

@interface SearchDisplayController : NSObject<UISearchBarDelegate>

- (id)initWithSearchBar:(UISearchBar *)searchBar contentsController:(UIViewController *)viewController;

@property(nonatomic,assign)                           id<SearchDisplayDelegate> delegate;

@property(nonatomic,getter=isActive)  BOOL            active;  // configure the view controller for searching. default is NO. animated is NO
- (void)setActive:(BOOL)visible animated:(BOOL)animated;       // animate the view controller for searching

@property(nonatomic,readonly)                         UISearchBar                *searchBar;
@property(nonatomic,readonly)                         UIViewController           *searchContentsController; // the view we are searching (often a UITableViewController)
@property(nonatomic,readonly)                         UITableView                *searchResultsTableView;   // will return non-nil. create if requested
@property(nonatomic,assign)                           id<UITableViewDataSource>   searchResultsDataSource;  // default is nil. delegate can provide
@property(nonatomic,assign)                           id<UITableViewDelegate>     searchResultsDelegate;

@end


@protocol SearchDisplayDelegate <NSObject>
// implement the protocols you need
@optional
@end

the implementation

@implementation SearchDisplayController {
    UISearchBar *_searchBar;
    UIViewController *_viewController;
    UITableView *_searchResultsTableView;
    UIView *_overLay;
}

- (void)dealloc {
    [_searchBar release];
    [_searchResultsTableView release];
    [_overLay release];
    [super dealloc];
}

- (UIViewController *)searchContentsController {
    return _viewController;
}

- (UITableView *)searchResultsTableView {
    return _searchResultsTableView;
}

- (id)initWithSearchBar:(UISearchBar *)searchBar contentsController:(UIViewController *)viewController {
    self = [super init];
    if (self) {
        _searchBar = [searchBar retain];
        _searchBar.delegate = self;
        _viewController = viewController;
        _searchResultsTableView = [[UITableView alloc]initWithFrame:CGRectMake(0, CGRectGetMaxY(_searchBar.frame), _viewController.view.frame.size.width, _viewController.view.frame.size.height - CGRectGetMaxY(_searchBar.frame))];
        _overLay = [[UIView alloc]initWithFrame:_searchResultsTableView.frame];
        _overLay.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(overLayTapped)];
        [_overLay addGestureRecognizer:tap];
        [tap release];
    }
    return self;
}
- (void)setSearchResultsDataSource:(id<UITableViewDataSource>)searchResultsDataSource {
    _searchResultsTableView.dataSource = searchResultsDataSource;
}

- (void)setSearchResultsDelegate:(id<UITableViewDelegate>)searchResultsDelegate {
    _searchResultsTableView.delegate = searchResultsDelegate;
}

- (void)overLayTapped {
    [self setActive:NO animated:YES];
    [_searchBar resignFirstResponder];
    _searchBar.text = nil;
    _searchBar.showsCancelButton = NO;
}

- (void)setActive:(BOOL)visible animated:(BOOL)animated {
    UIView *viewToAdd = nil;
    if (!_searchBar.text.length) {
        viewToAdd = _overLay;
    } else {
        viewToAdd = _searchResultsTableView;
    }
    float a = 0;
    if (visible) {
        [_viewController.view addSubview:viewToAdd];
        a = 1.0;
    }
    if ([_viewController.view respondsToSelector:@selectore(scrollEnabled)]) {
        ((UIScrollView *)_viewController.view).scrollEnabled = !visible;
    }

    if (animated) {
        [UIView animateWithDuration:0.2 animations:^{
            _overLay.alpha = a;
            _searchResultsTableView.alpha = a;
        }];
    } else {
        _overLay.alpha = a;
        _searchResultsTableView.alpha = a;
    }
}

- (void)setActive:(BOOL)active {
    [self setActive:active animated:YES];
}

#pragma mark - UISearchBar delegate protocols

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
    [self setActive:YES animated:YES];
    searchBar.showsCancelButton = YES;
    [_searchResultsTableView reloadData];
}

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

}

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
    [_searchResultsTableView reloadData];
}

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
    [self overLayTapped];
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    if (searchText.length) {
        [_overLay removeFromSuperview];
        [_viewController.view addSubview:_searchResultsTableView];
    } else {
        [_searchResultsTableView removeFromSuperview];
        [_viewController.view addSubview:_overLay];
    }
        [_searchResultsTableView reloadData];
}

@end

Update: on how to use this progammatically

declare an ivar

SearchDisplayController *mySearchDisplayController;

initialize it programmatically

mySearchDisplayController = [[SearchDisplayController alloc]initWithSearchBar:mySearchBar contentsController:self];

adding the searchbar to your tableview

self.tableView.headerView = mySearchBar;

use mySearchDisplayController as reference to the custon class instead on self.searchDisplayController.

tzl
  • 1,540
  • 2
  • 20
  • 31
  • Actually I am also using a custom class for searchBar and searchDisplaycontroller delegate functions. I assign searchDisplayController to this class in xib of the above view controller. – tech savvy Oct 09 '13 at 10:03
  • sorry I missed the part where you said you use xib. I use this programmatically. a custom class for searchBar should be fine as long as its a subclass of UISearchbar – tzl Oct 10 '13 at 03:11
  • I created a custom class which is child class of searchDisplayController like what you have done above. I assign THIS class to searchDisplayController in xib file. How to assign it programtically? – tech savvy Oct 11 '13 at 10:06
  • if I create a searchBar programatically and add it works fine. However, if I use UISearchBar *searchBar = self.searchDisplayController.searchBar; and put this in headerView the problem comes. – tech savvy Oct 11 '13 at 11:12
  • 1
    self.searchDisplayController is a readonly property. If uninitialized programmatically or via xib, it points to nil. initialize an ivar for the custom searchdisplaycontroller and use that as reference in your code. ignore self.searchDisplayController. see updated post. have updated the search display implementation also. – tzl Oct 12 '13 at 07:38
2


In my case, the table view that held the search display controller's search bar in its header view was being reloaded almost as soon as the view appeared. It was at this point that the search bar would cease to render. When I scrolled the table, it would reappear. It's also worth mentioning that my table contained a UIRefreshControl and was not a UITableViewController subclass.

My fix involved setting the search display controller active and then inactive very quickly just before after loading the table (and ending the refresh control refreshing):

[self.tableView reloadData];    
[self.refreshControl endRefreshing];
[self.searchDisplayController setActive:YES animated:NO];
[self.searchDisplayController setActive:NO];


A bit of a hack but it works for me.

imnk
  • 4,342
  • 3
  • 29
  • 31
2

Using the debugger, I've found that the UISearchBar is initially a child view of the tableHeaderView - but when it disappears, it has become a child of the tableView itself. This has probably been done by UISearchDisplayController somehow... So I did the following hack to simply return the UISearchBar to the header view:

- (void)viewWillAppear:(BOOL)animated
{
  [super viewWillAppear:animated];
  if(!self.searchDisplayController.isActive && self.searchBar.superview != self.tableView.tableHeaderView) {
    [self.tableView.tableHeaderView addSubview:self.searchBar];
  }
}

Seems to work fine on iOS 7 as well as 6 :)

(checking that the searchDisplayController isn't active is necessary, otherwise the sarch bar disappears during search)

2

I endorse Phien Tram's answer. Please upvote it. I don't have enough points myself.

I had a similar problem where a search bar loaded from storyboard would disappear when I repeatedly tapped it, invoking and dismissing search. His solution repairs the problem.

There seems to be a bug where repeated invocation and dismissal of the search display controller doesn't always give the search bar back to the table view.

I will say I'm uncomfortable with the solution's dependence on the existing view hierarchy. Apple seems to reshuffle it with every major release. This code may break with iOS 8.

I think a permanent solution will require a fix by Apple.

Community
  • 1
  • 1
Bob Wakefield
  • 814
  • 9
  • 16
  • The cancel button not working appears to have been fixed as of 7.1 beta 2, but I still have a similar issue with the search bar disappearing. – aednichols Jan 07 '14 at 14:10
1

I had the same issue and I could fix it calling next line after creating the UISearchDisplayController

[self performSelector:@selector(setSearchDisplayController:) withObject:displayController];

My viewDidLoad function look like this:

UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, 44)];
searchBar.placeholder = NSLocalizedString(@"Search", @"Search");

UISearchDisplayController *displayController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
displayController.delegate = self;
displayController.searchResultsDataSource = self;
displayController.searchResultsDelegate = self;

[self performSelector:@selector(setSearchDisplayController:) withObject:displayController];

self.tableView.contentOffset = CGPointMake(0, 44);
self.tableView.tableHeaderView = searchBar;

Thanks to this answer: https://stackoverflow.com/a/17324921/1070393

Community
  • 1
  • 1
RemeR
  • 667
  • 8
  • 20
1

I've faced similar problem and after some digging, I've found that this is a bug in UISearchBar hierarchy. This hacky solution worked for me in iOS 7, but be aware that this may break in future iOS versions:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    UIView *buggyView = [self.searchDisplayController.searchBar.subviews firstObject];
    // buggyView bounds and center are incorrect after returning from controller, so adjust them.
    buggyView.bounds = self.searchDisplayController.searchBar.bounds;
    buggyView.center = CGPointMake(CGRectGetWidth(buggyView.bounds)/2, CGRectGetHeight(buggyView.bounds)/2);
}
Maxim Pavlov
  • 2,962
  • 1
  • 23
  • 33
1

Use UISearchBar above UITableView,Then make IBOutlet for and connect them with file's owner to UISearchbar

Example- .h file

#import <UIKit/UIKit.h>

@interface LocationViewController : UIViewController<UISearchBarDelegate>
{

   BOOL IsSearchOn;

}

@property (strong, nonatomic) IBOutlet UISearchBar *searchBar;

@property (strong, nonatomic) IBOutlet UITableView *TBVLocation;

.m file

#pragma mark -


#pragma mark UISearchBar Delegate Methods



-(void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText

{

    [self.searchResult removeAllObjects];

     if(searchText.length == 0)
      {
           IsSearchOn=NO;
        // [filteredTableData removeAllObjects];
           [self.searchBar resignFirstResponder];
        // [self .tblView reloadData];


     }
   else
     {
       IsSearchOn=YES;

       if(searchText != nil && ![searchText isEqualToString:@""])
       {

        /*            NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@", searchText];

        self.searchResult = [NSMutableArray arrayWithArray: [searchArray filteredArrayUsingPredicate:resultPredicate]];*/
        for(int i=0;i<[[arrCountryList valueForKey:@"country_name"] count];i++)
        {


            NSRange titleRange =    [[[[arrCountryList valueForKey:@"country_name"] objectAtIndex:i] lowercaseString] rangeOfString:[searchText lowercaseString]];

            if(titleRange.location != NSNotFound)
                [self.searchResult addObject:[arrCountryList objectAtIndex:i]];

        }

        [TBVLocation reloadData];
    }
   }

 }



- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar

 {


 [searchBar resignFirstResponder];


 }


-(void)searchBarCancelButtonClicked:(UISearchBar *) searchBar


{


  [searchBar resignFirstResponder];

  IsSearchOn=NO;

  searchBar.text = nil;

  [TBVLocation reloadData];



}

- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar

{

  [searchBar resignFirstResponder];
  return YES;
}

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
   searchBar.showsCancelButton = YES;
   searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
   // IsSearchOn=YES;
}
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar

{
    IsSearchOn=NO;
    searchBar.showsCancelButton = NO;
   [TBVLocation reloadData];
   [searchBar resignFirstResponder];
}

It will work like charm.

Anuj Kumar Rai
  • 666
  • 1
  • 6
  • 17
0

I had the same problem and tested some of the solutions proposed here in this thread, but they didn't solve the problem for me. Previously, I added and configured the UISearchBar in the

 - (void)viewDidLoad

method of my ViewController in code.

UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:searchBarView.frame];
searchBar.delegate = self;
searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
ect...

What solved this issue for me was that I added a UISearchbar in the InterfaceBuilder, created an outlet in my ViewController and added this UISearchBar to my UISearchDisplayController.

self.searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar(<--outlet) contentsController:self];

hope this might also help some people

Heckscheibe
  • 630
  • 7
  • 8