Question

I have a root view controller which composes a search bar on the top and a child table view controller on the bottom. I used composition instead of assigning the search bar to the table view's header for these reasons:

  1. I didn't want the index to overlap with the search bar (like Contacts app).
  2. I wanted the search bar to be sticky. That is, it doesn't move when I scroll the table view (again like the Contacts app).
  3. My table view already had a header.

Since the search bar is in the root view controller, I instantiate my search display controller in the root view controller also. There are two problems with the search UI for which I seek advice:

  1. The translucent gray overlay does not cover the entire child table view. It leaves the top portion of the header and the index visible.
  2. Likewise, the search results table does not cover the entirety of the child table view. I know how to manually change the frame of this results table view, but doing so only fixes just that ... the gray translucent overlay's frame is not linked to the results table view frame. Their is no property to access the overlay.

1) Idle

enter image description here

2) Enter Search Bar

enter image description here

3) Start Typing

enter image description here

#import "ContactsRootViewController.h"
#import "ContactsViewController.h"
#import "UIView+position.h"
#import "User.h"
#import "UserCellView.h"
#import "UserViewController.h"

@interface ContactsRootViewController ()

@property(nonatomic, strong) UISearchBar* searchBar;
@property(nonatomic, strong) ContactsViewController* contactsViewController;
@property(nonatomic, strong) UISearchDisplayController* searchController;
@property(nonatomic, strong) NSMutableArray* matchedUsers;

@end

@implementation ContactsRootViewController

#pragma mark UIViewController

- (NSString*)title
{
    return @"Contacts";
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.matchedUsers = [NSMutableArray array];

    self.searchBar = [[UISearchBar alloc] init];
    self.searchBar.placeholder = @"Search";
    [self.searchBar sizeToFit];
    [self.view addSubview:self.searchBar];
}

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

    if (self.contactsViewController == nil) {
        self.contactsViewController = [[ContactsViewController alloc] init];
        [self addChildViewController:self.contactsViewController];
        self.contactsViewController.view.frame = CGRectMake(
            0.0,
            self.searchBar.bottomY,
            self.view.frame.size.width,
            self.view.frame.size.height - self.searchBar.bottomY
        );
        self.contactsViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
        [self.view addSubview:self.contactsViewController.view];
        [self.contactsViewController didMoveToParentViewController:self];

        self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self.contactsViewController];
        self.searchController.delegate = self;
        self.searchController.searchResultsDataSource = self;
        self.searchController.searchResultsDelegate = self;
    }
}

#pragma mark UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.matchedUsers.count;
}

- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString* identifier = @"contactsRootViewUserCell";
    UserCellView* cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    if (cell == nil) {
        cell = [[UserCellView alloc] initWithIdentifier:identifier];
    }
    cell.user = [self.matchedUsers objectAtIndex:indexPath.row];
    return cell;
}

#pragma mark UITableViewDelegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self.navigationController pushViewController:[[UserViewController alloc] initWithUser:[self.matchedUsers objectAtIndex:indexPath.row]] animated:YES];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [UserCellView height];
}

#pragma mark UISearchDisplayControllerDelegate

- (BOOL)searchDisplayController:(UISearchDisplayController*)controller shouldReloadTableForSearchString:(NSString *)searchString
{
    [self.matchedUsers removeAllObjects];

    searchString = [searchString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

    if (searchString.length > 0) {
        for (User* user in self.contactsViewController.allUsers) {
            NSRange match = [user.userDisplayName rangeOfString:searchString options:NSCaseInsensitiveSearch];
            if (match.location != NSNotFound) {
                [self.matchedUsers addObject:user];
            }
        }
    }

    return YES;
}

- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller
{
    [self.searchBar resignFirstResponder];
}

@end
Was it helpful?

Solution

I re-implemented UISearchDisplayController, calling my implementation SearchController. It does the same thing and has similar delegate callbacks, but the frame of the search results can be controlled by the programmer.

Header

#import <Foundation/Foundation.h>

@class SearchController;

@protocol SearchControllerDelegate <NSObject>

@required
- (BOOL)searchController:(SearchController*)controller shouldReloadTableForSearchString:(NSString*)searchText;

@optional
- (void)searchController:(SearchController*)controller didShowSearchResultsTableView:(UITableView*)tableView;
- (void)searchController:(SearchController *)controller didHideSearchResultsTableView:(UITableView *)tableView;
- (void)searchControllerDidBeginSearch:(SearchController*)controller;
- (void)searchControllerDidEndSearch:(SearchController*)controller;

@end

@interface SearchController : UIViewController <UISearchBarDelegate>

@property(nonatomic, weak) NSObject<SearchControllerDelegate>* delegate;
@property(nonatomic, weak) NSObject<UITableViewDataSource>* searchResultsDataSource;
@property(nonatomic, weak) NSObject<UITableViewDelegate>* searchResultsDelegate;
@property(nonatomic, strong, readonly) UITableView* searchResultsTableView;

- (id)initWithSearchBar:(UISearchBar*)searchBar;

@end

Implementation

#import "SearchController.h"
#import "UIView+position.h"

@interface SearchController ()

@property(nonatomic, strong) UISearchBar* searchBar;
@property(nonatomic, strong) UIButton* searchResultsVeil;
@property(nonatomic, strong, readwrite) UITableView* searchResultsTableView;
@property(nonatomic, assign) BOOL searchResultsTableViewHidden;

- (void)didTapSearchResultsVeil;
- (void)hideSearchResults;

@end

@implementation SearchController

#pragma mark UIViewController

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

    [self.searchResultsTableView deselectRowAtIndexPath:[self.searchResultsTableView indexPathForSelectedRow] animated:YES];
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.view.userInteractionEnabled = NO;
}

#pragma mark SearchController ()

- (void)hideSearchResults
{
    self.searchBar.text = nil;
    [self.searchResultsTableView reloadData];
    self.searchResultsTableViewHidden = YES;
    [self.searchBar resignFirstResponder];
}

- (void)didTapSearchResultsVeil
{
    [self hideSearchResults];
}

- (void)setSearchResultsTableViewHidden:(BOOL)searchResultsTableViewHidden
{
    if (self.searchResultsTableView != nil) {
        if (self.searchResultsTableView.hidden && !searchResultsTableViewHidden) {
            self.searchResultsTableView.hidden = searchResultsTableViewHidden;
            if ([self.delegate respondsToSelector:@selector(searchController:didShowSearchResultsTableView:)]) {
                [self.delegate searchController:self didShowSearchResultsTableView:self.searchResultsTableView];
            }
        } else if (!self.searchResultsTableView.hidden && searchResultsTableViewHidden) {
            self.searchResultsTableView.hidden = searchResultsTableViewHidden;
            if ([self.delegate respondsToSelector:@selector(searchController:didHideSearchResultsTableView:)]) {
                [self.delegate searchController:self didHideSearchResultsTableView:self.searchResultsTableView];
            }
        }
    }
}

- (BOOL)searchResultsTableViewHidden
{
    return self.searchResultsTableView == nil || self.searchResultsTableView.hidden;
}

#pragma mark SearchController

- (id)initWithSearchBar:(UISearchBar *)searchBar
{
    if (self = [super init]) {
        self.searchBar = searchBar;
        self.searchBar.delegate = self;
    }
    return self;
}

- (void)setSearchResultsDataSource:(NSObject<UITableViewDataSource> *)searchResultsDataSource
{
    _searchResultsDataSource = searchResultsDataSource;
    if (self.searchResultsTableView != nil) {
        self.searchResultsTableView.dataSource = searchResultsDataSource;
    }
}

- (void)setSearchResultsDelegate:(NSObject<UITableViewDelegate> *)searchResultsDelegate
{
    _searchResultsDelegate = searchResultsDelegate;
    if (self.searchResultsTableView != nil) {
        self.searchResultsTableView.delegate = searchResultsDelegate;
    }
}

#pragma mark UISearchBarDelegate

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    if ([self.delegate searchController:self shouldReloadTableForSearchString:searchText]) {
        [self.searchResultsTableView reloadData];
        self.searchResultsTableViewHidden = [self.searchResultsTableView.dataSource tableView:self.searchResultsTableView numberOfRowsInSection:0] == 0;
    }
}

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
    [searchBar setShowsCancelButton:YES animated:YES];
    if (self.searchResultsVeil == nil) {
        self.searchResultsVeil = [[UIButton alloc] initWithFrame:self.view.bounds];
        self.searchResultsVeil.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.6];
        self.searchResultsVeil.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        [self.searchResultsVeil addTarget:self action:@selector(didTapSearchResultsVeil) forControlEvents:UIControlEventTouchUpInside];

        self.searchResultsTableView = [[UITableView alloc] initWithFrame:self.searchResultsVeil.bounds style:UITableViewStylePlain];
        self.searchResultsTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        if ([self.searchResultsTableView respondsToSelector:@selector(setSeparatorInset:)]) {
            self.searchResultsTableView.separatorInset = UIEdgeInsetsMake(
                0.0,
                self.searchResultsTableView.width,
                0.0,
                0.0
            );
        }
        self.searchResultsTableViewHidden = YES;
        if (self.searchResultsDataSource != nil) {
            self.searchResultsTableView.dataSource = self.searchResultsDataSource;
        }
        if (self.searchResultsDelegate != nil) {
            self.searchResultsTableView.delegate = self.searchResultsDelegate;
        }

        [self.view addSubview:self.searchResultsVeil];
        [self.searchResultsVeil addSubview:self.searchResultsTableView];
    }
    self.view.userInteractionEnabled = YES;
    self.searchResultsVeil.hidden = NO;

    if ([self.delegate respondsToSelector:@selector(searchControllerDidBeginSearch:)]) {
        [self.delegate searchControllerDidBeginSearch:self];
    }
}

- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
    [searchBar setShowsCancelButton:NO animated:YES];
    self.view.userInteractionEnabled = NO;
    self.searchResultsVeil.hidden = YES;

    if ([self.delegate respondsToSelector:@selector(searchControllerDidEndSearch:)]) {
        [self.delegate searchControllerDidEndSearch:self];
    }
}

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

@end

Usage

self.searchController = [[SearchController alloc] initWithSearchBar:self.searchBar];
self.searchController.delegate = self;
self.searchController.searchResultsDataSource = self;
self.searchController.searchResultsDelegate = self;
[self addChildViewController:self.searchController];
self.searchController.view.frame = CGRectMake(
    self.searchBar.x,
    self.searchBar.bottomY,
    self.searchBar.width,
    self.view.height - self.searchBar.bottomY
);
[self.view addSubview:self.searchController.view];
[self.searchController didMoveToParentViewController:self];

OTHER TIPS

It looks like your view controller does not define a presentation context. I had a similar problem and was able to resolve it by setting

self.definesPresentationContext = YES;

in viewDidLoad. According to the documentation this property is

A Boolean value that indicates whether this view controller's view is covered when the view controller or one of its descendants presents a view controller.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top