[iOS] Interface Builder を使わないで UISearchBar を追加してみた

[iOS] Interface Builder を使わないで UISearchBar を追加してみた

よくTwitterアプリのタイムライン検索などで使われている検索バーがありますが、けっこうサクッと追加できます。

UISearchBar を使う

検索バーにフォーカスすると自動的にナビゲーションバーが隠れて [cancel] ボタンが表示されます。
更に、文字を入力していくとテーブルの中身が絞り込まれていく仕組みが用意されています。

検索バーにフォーカス

どうやって絞り込んでいくかは、- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope の中身をごにょごにょすればいい。

- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
    // ごにょごにょ
}

UITableViewController に UISearchBar を追加

以下のソースは余分なものを削っています。

RootViewController.h

@interface RootViewController : UITableViewController <UISearchDisplayDelegate, UISearchBarDelegate> {
    NSArray *_items;
    NSMutableArray *_filteredListContent;
}
@end

RootViewController.m

#import "RootViewController.h"

@implementation RootViewController

#pragma mark - View lifecycle

- (void)viewDidLoad {
    [super viewDidLoad];

    self.title = @"Products";

    // InterfaceBuilderを使わずに view を作成
    self.view = [[UIView alloc] initWithFrame:CGRectZero];
    [self.view release];

    // InterfaceBuilderを使わずに tableView を作成
    self.tableView = [[UITableView alloc] initWithFrame:CGRectZero];
    self.tableView.separatorColor = [UIColor colorWithRed:0.91 green:0.91 blue:0.91 alpha:1.0];
    [self.tableView release];

    // InterfaceBuilderを使わずに UISearchBar を追加
    UISearchBar *searchBar;
    searchBar = [[[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 44.0f)] autorelease];
    searchBar.showsCancelButton = YES;
    searchBar.tintColor = [UIColor lightGrayColor];
    searchBar.delegate = self;
    searchBar.placeholder = @"検索ワードを入力してください";
    searchBar.showsCancelButton = NO;
    [searchBar sizeToFit];
    self.tableView.tableHeaderView = searchBar;

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

    // 配列を生成
    _items = [[NSArray alloc] initWithObjects:@"iPhone", @"iPod", @"iPod touch", @"iMac", @"Mac Pro", @"iBook", @"MacBook", @"MacBook Pro", @"PowerBook", nil];
    _filteredListContent = [[NSMutableArray arrayWithCapacity:[_items count]] retain];

    [self.tableView reloadData];
    self.tableView.scrollEnabled = YES;
}

- (void)viewDidUnload {
    _filteredListContent = nil;
    [super viewDidUnload];
}

- (void)dealloc {
    [_items release];
    [_filteredListContent release];
    [super dealloc];
}
#pragma mark - Table view data source

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (tableView == self.searchDisplayController.searchResultsTableView) {
        return [_filteredListContent count];
    }
    return [_items count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }

    NSString *label;
    if (tableView == self.searchDisplayController.searchResultsTableView) {
        label = [_filteredListContent objectAtIndex:indexPath.row];
    } else {
        label = [_items objectAtIndex:indexPath.row];
    }
    cell.textLabel.text = label;
    return cell;
}

#pragma mark - Table view delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    UIViewController *detailsViewController = [[UIViewController alloc] init];
    NSString *label;
    if (tableView == self.searchDisplayController.searchResultsTableView) {
        label = [_filteredListContent objectAtIndex:indexPath.row];
    } else {
        label = [_items objectAtIndex:indexPath.row];
    }
    detailsViewController.title = label;
    [[self navigationController] pushViewController:detailsViewController animated:YES];
    [detailsViewController release];
}

#pragma mark - Content Filtering

- (void)filterContentForSearchText:(NSString *)searchText scope:(NSString *)scope {
    [_filteredListContent removeAllObjects];
    for (NSString *label in _items) {
        NSComparisonResult result = [label compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
        if (result == NSOrderedSame) {
            [_filteredListContent addObject:label];
        }
    }
}

#pragma mark - UISearchDisplayController Delegate Methods

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
    [self filterContentForSearchText:searchString scope:[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
    return YES;
}

@end

文字を入力して絞り込み検索

ソースコードは、iOS Developer Library にあったものを参考にしました。

TableSearch

最後に

苦労したのは、UISearchDisplayController を生成するところ。
UIViewController にあらかじめ用意されている self.searchDisplayController が readonly だったので、それを直接書き換えることができず…。

どうやら、UISearchDisplayController を初期化メソッドで生成すれば自動的に認識してくれるらしいです。