I've search endlessly online for a solution to this problem, but nothing that was recommended worked in my case. Resetting the searchResultsTable's frame didn't work because it's origin.y was already at 0. Changing the contentInset kind of worked, but didn't fix the dimmed overlay view and caused issues with the table's scroll view at the bottom (and bars). I did finally get a better working hack, although it's not completely ideal since the view's frame shifts are noticeable briefly, but at least positioning is correct after that.
Using Reveal.app, I was able to dig into the view hierarchy of UISearchDisplayController to figure out what was going on, and this was the result in my case:
UISearchDisplayControllerContainerView
- UIView (0,0,320,504)
- UISearchResultsTableView
- UIView (0,20,320,44)
- UIView (0,64,320,440)
- _UISearchDisplayControllerDimmingView
I do everything programmatically, so not sure about a workaround for NIBs. Here are the basics of how my UISearchBar and UISearchDisplayController are setup in my viewDidLoad
method:
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 0)];
searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
searchBar.delegate = self;
[searchBar sizeToFit];
self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
self.searchController.delegate = self;
self.searchController.searchResultsDataSource = self.searchDataSource;
self.searchController.searchResultsDelegate = self;
if ([self.searchController respondsToSelector:@selector(displaysSearchBarInNavigationBar)]) {
self.searchController.displaysSearchBarInNavigationBar = YES;
}
And my hack that worked in this case:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fixSearchControllerPositionOnKeyboardAppear)
name:UIKeyboardWillShowNotification object:nil];
if (self.searchController.isActive) {
// the following is needed if you are return to this controller after dismissing the child controller displayed after selecting one of the search results
[self performSelector:@selector(fixSearchControllerPositionForiOS7) withObject:nil afterDelay:0];
}
}
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
}
- (void)fixSearchControllerPositionForiOS7 {
UIView *view = self.searchController.searchResultsTableView.superview;
// only perform hack if the searchResultsTableView has been added to the view hierarchy
if (view) {
// The searchDisplayController's container view is already at 0,0, but the table view if shifted down 64px due to
// bugs with the subviews in iOS 7, so shift the container back up by that negative offset.
// This also fixes the position of the dimmed overlay view that appears before results are returned.
CGFloat yOffset = 64.0;
CGRect viewFrame = view.frame;
if (CGRectGetMinY(viewFrame) == 0) {
viewFrame.origin.y = -yOffset;
viewFrame.size.height += yOffset;
[UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
view.frame = viewFrame;
} completion:nil];
}
// we also need to adjust dimmed overlay view, so iterate through the search view controller's container
// view and make sure all subviews have their vertical origin set to 0
UIView *searchContainerView = view.superview;
for (NSInteger i = 0; i < [searchContainerView.subviews count]; i++) {
UIView *subview = searchContainerView.subviews[i];
if (CGRectGetMinY(subview.frame) > 0) {
CGRect subviewFrame = subview.frame;
CGFloat offset = CGRectGetMinY(subviewFrame);
subviewFrame.origin.y = 0;
if (offset == 20.0) {
// this subview is partially responsible for the table offset and overlays the top table rows, so set it's height to 0
subviewFrame.size.height = 0;
}
else {
// this subview is the dimmed overlay view, so increase it's height by it's original origin.y so it fills the view
subviewFrame.size.height += offset;
}
subview.frame = subviewFrame;
}
}
}
}
- (void)fixSearchControllerPositionOnKeyboardAppear {
// call hack to reset position after a slight delay to avoid UISearchDisplayController from overriding our layout fixes
[self performSelector:@selector(fixSearchControllerPositionForiOS7) withObject:nil afterDelay:0.1];
}
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) {
[self fixSearchControllerPositionForiOS7];
}
}
I had to add an observer for when the keyboard appears as this was causing the UISearchDisplayController to re-layout its subviews, along with a short delay to ensure my position adjustments were applied after UISearchDisplayController did it's layout stuff.