7

As shown below, my UISearchBar is getting resized when I tap on the search field. It animates nicely to cover the navigation bar, and then pop... it shrinks.

The setup

The UISearchBar is inside a vanilla UIView set as the tableHeaderView. I'm using a UIView (as opposed to setting the UISearchBar as the header) because I would like to put additional views in the header.

The view is defined in a XIB file and the UISearchBar is anchored to all of its borders. The constraints don't seem to matter; if I remove them, the same problem happens.

Experiments

Examining the view hierarchy in Reveal tells me that the UIView has the right size and the UISearchBar has width 1 (?!) when this happens.

As an experiment, I subclassed the UISearchBar and made intrinsicContentSize return {320,44}. With this the UISearchBar shows properly, but when I press Cancel I get the following exception:

 *** Assertion failure in -[UITableView layoutSublayersOfLayer:], /SourceCache/UIKit_Sim/UIKit-2903.23/UIView.m:8540
 *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super.'

The workaround

If I create everything by code, it just works.

_headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 44)];
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 44)];
searchBar.delegate = self;
[_headerView addSubview:searchBar];

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

_tableView.tableViewHeader = _headerView;

What is going on here? I clearly won't use nibs to work with UISearchBar anymore, but I would like to understand what the heck happened anyway.

hpique
  • 119,096
  • 131
  • 338
  • 476

2 Answers2

15

The difference is in the search bar's value of translatesAutoresizingMaskIntoConstraints. It defaults to NO for the XIB-based view and YES for the code-based view. Apparently, the search controller assumes a value of YES and doesn't set up any explicit constraints when it transplants the search bar.

Setting the value to YES in code should fix it (verified locally).

UPDATE

As noted in the comments, even with translatesAutoresizingMaskIntoConstraints = YES, the controller does not restore the header view to it's original state on cancel. But there does seem to be a workaround. You can create an outlet to your header view and restore it yourself in searchDisplayControllerDidEndSearch. I did a crude proof-of-concept (and updated my sample project):

- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller
{
    self.tableView.tableHeaderView = self.headerView;
    [self.searchBar removeFromSuperview];
    self.searchBar.frame = CGRectMake(0, 20, 320, 44);
    [self.headerView addSubview:self.searchBar];
}
Timothy Moose
  • 9,895
  • 3
  • 33
  • 44
  • The are no autoresizing masks in the code I posted and it also works. – hpique Jan 29 '14 at 03:57
  • Yeah, but the default value of `UIViewAutoresizingNone` is still a value. And it means "don't resize", which can be translated into equivalent constraints. But don't take my word for it, try this [sample project](https://dl.dropboxusercontent.com/u/2183704/Search%20Bar%20Demo.zip) with and without the `self.searchBar.translatesAutoresizingMaskIntoConstraints = YES` in `viewDidLoad`. – Timothy Moose Jan 29 '14 at 05:08
  • So my sample project didn't convince you? – Timothy Moose Jan 30 '14 at 04:15
  • Thanks Timothy. It looks like this is bug, doesn't it? It shouldn't be necessary to use `translatesAutoresizingMaskIntoConstraints` when using IB. – hpique Jan 30 '14 at 11:09
  • 5
    No doubt a bug. I filed a couple of bug reports with Apple. The second bug was that (at least in my updated sample project), even with `translatesAutoresizingMaskIntoConstraints = YES`, the original layout is not restored after tapping Cancel. It seems that `UISearchDisplayController` makes an undocumented assumption that the search bar is the only content of the header view. – Timothy Moose Jan 30 '14 at 18:43
  • It's worse. It actually replaces your header with the UISearchBar. – hpique Jan 30 '14 at 19:04
  • Anyway to get around replacing the header? When I get out of the search bar mode, it re-sizes to it's super view covering all other views in that view. – jgvb Feb 05 '14 at 19:02
  • @James Updated my answer with a workaround that I think may work for you. – Timothy Moose Feb 05 '14 at 20:21
  • @TimothyMoose this is absolutely terrible. i've wasted hours today with this. i hate having to implement these hacks but i appreciate your help in figuring this out. has anyone made progress since this? – MrTristan Mar 24 '14 at 22:44
  • by the way, setting translatesAutoresizingMaskIntoConstraints doesn't do anything different for me, and setting it to NO makes the search bar shrink to almost nothing and then crashes the app... upvoting for pushing me further towards the idea of implementing searchDisplayControllerDidEndSearch and recreating table header. – MrTristan Mar 24 '14 at 22:47
1

Following on from what both @TimothyMoose and @hpique have said about setting translatesAutoresizingMaskIntoConstraints = YES (yuck)

In my code I have found that if I do the following to show the searchDisplayController

self.searchDisplayController.searchBar.translatesAutoresizingMaskIntoConstraints = YES
[self.searchDisplayController setActive:YES animated:YES] 

and do the opposite when closing the searchDisplayController,

self.searchDisplayController.searchBar.translatesAutoresizingMaskIntoConstraints = NO
[self.searchDisplayController setActive:NO animated:YES] 

Then my view remains unchanged (everything goes back to what I'm expecting)

if i do not call the following line when closing

self.searchDisplayController.searchBar.translatesAutoresizingMaskIntoConstraints = NO

Then the view screws up.

The reason for this I believe is that the NSAutoresizingMaskLayoutConstraint are asserting them self and as they are the last in the constraint list, the Layout engine brakes the real constraint to satisfy the unwanted NSAutoresizingMaskLayoutConstraint constraints.

Edys
  • 71
  • 7
  • This seem to work correctly building against iOS8 beta 5, and running on 7.1, 8 beta 5 NO search bar seem to appear at all in iOS6... :( - i believe the search bar is off the screen – Edys Sep 03 '14 at 04:25
  • and sort of on iOS6 (seems other code in the code base was interfering) – Edys Sep 03 '14 at 04:42