SearchBar disappears from headerview in iOS 7

11,688

Solution 1

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

Solution 2

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.

Solution 3

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.

Solution 4

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)

Solution 5


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.

Share:
11,688

Related videos on Youtube

tech savvy
Author by

tech savvy

Updated on June 29, 2022

Comments

  • tech savvy
    tech savvy almost 2 years

    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.

  • tech savvy
    tech savvy over 10 years
    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.
  • tzl
    tzl over 10 years
    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
  • tech savvy
    tech savvy over 10 years
    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
    tech savvy over 10 years
    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.
  • tzl
    tzl over 10 years
    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.
  • aednichols
    aednichols over 10 years
    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.
  • Tyrael Tong
    Tyrael Tong over 9 years
    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!