【Xcode】TableViewセルの検索機能を実装する

この記事について

環境Xcode、Objective-C、Storyboard
目的TableViewにSearch Bar and Search Displayで検索機能を付ける
結果searchDisplayControllerで検索中/通常時のテーブルビューを出し分けて実装できた

つまずきポイント要約

  • プロトコルが未実装だと「Assigning to id<~> from incompatible type」エラーが出る
  • 1度目のタップで反応しないときはdidDeselectRowAtIndexPathになっていないか確認

1.TableView と Search Bar and Search Display を使うための下準備をする

1.プロジェクトからシングルビューをつくり、ストーリーボード上の ViewController に TableView と Search Bar and Search Display を配置する。

2.配置した2つの View(TableView と Search Bar and Search Display)を ViewController.mへそれぞれ接続する。

3.プロトコルを追加。

//ViewController.h(m)

@interface ViewController : UIViewController<UITableViewDataSource, UITableViewDelegate, UISearchDisplayDelegate, UISearchBarDelegate>

//searchBar を接続(ストーリーボードからドラッグして行う)。
@property (weak, nonatomic) IBOutlet UISearchBar *searchBar;

//TableView を接続(ストーリーボードからドラッグして行う)。
@property (weak, nonatomic) IBOutlet UITableView *tableView;

@end

2.テーブルビューのセルにデータを表示させる

以下の記述をファイルに追加する。

//ViewController.h(m)

@interface ViewController : UIViewController<UITableViewDataSource, UITableViewDelegate, UISearchDisplayDelegate, UISearchBarDelegate>

//searchBar を接続
@property (weak, nonatomic) IBOutlet UISearchBar *searchBar;

//TableView を接続
@property (weak, nonatomic) IBOutlet UITableView *tableView;

/**
 *  さきほど記述した場所の下に追記する。テーブルに表示する情報が入ります
 */
@property (nonatomic, strong) NSArray *dataSourceiPhone;
@property (nonatomic, strong) NSArray *dataSourceAndroid;

@end

これを追記したタイミングで「”Assigning to ‘id<~>‘ from incompatible type ‘~’ “」のようなエラーが出る場合は、プロトコルがきちんと実装できているか確認する。

//これも記述

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
 
    // デリゲートメソッドをこのクラスで実装する
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
 
    // テーブルに表示したいデータソースをセット
    self.dataSourceiPhone = @[@"iPhone 4", @"iPhone 4S", @"iPhone 5", @"iPhone 5c", @"iPhone 5s"];
    self.dataSourceAndroid = @[@"Nexus", @"Galaxy", @"Xperia"];
}

//これもまるまる追記

@implementation ViewController

(略)

//ここから下を全部追記

#pragma mark - TableViewDataSource delegate methods
 
/**
 *  テーブルに表示するデータ件数を返します(実装必須)
 *
 *  @param tableView テーブルビュー
 *  @param section   対象セクション番号
 *
 *  @return データ件数
 */
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger dataCount;
 
    switch (section) {
        case 0:
            dataCount = self.dataSourceiPhone.count;
            break;
        case 1:
            dataCount = self.dataSourceAndroid.count;
            break;
        default:
            break;
    }
    return dataCount;
}
 
/**
 *  テーブルに表示するセクション(区切り)の件数を返します(任意実装)
 *
 *  @param  テーブルビュー
 *
 *  @return セクション件数
 */
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 2;
}
 
/**
 *  テーブルに表示するセルを返します(実装必須)
 *
 *  @param tableView テーブルビュー
 *  @param indexPath セクション番号・行番号の組み合わせ
 *
 *  @return セル
 */
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    // 再利用できるセルがあれば再利用する
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
 
    if (!cell) {
        // 再利用できない場合は新規で作成
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:CellIdentifier];
    }
 
    switch (indexPath.section) {
        case 0:
            cell.textLabel.text = self.dataSourceiPhone[indexPath.row];
            break;
        case 1:
            cell.textLabel.text = self.dataSourceAndroid[indexPath.row];
            break;
        default:
            break;
    }
 
    return cell;
}


@end

セルにデータが表示てきていることを確認する。

3.検索バーをタップした時のTableViewControllerに検索結果を表示する準備をする

//検索結果を格納するNSArrayをプロパティで宣言する

@interface ViewController ()

@property (nonatomic, strong) NSArray *dataSourceiPhone;
@property (nonatomic, strong) NSArray *dataSourceAndroid;

//ここを追記
//検索結果を格納する配列
@property (nonatomic, strong) NSArray *dataSourceSearchResultsiPhone;
@property (nonatomic, strong) NSArray *dataSourceSearchResultsAndroid;

@end

結果を表示するViewControllerはStoryboardで 「Search Bar and Search Display」を紐づけした時に自動で参照が追加されて Self(UIViewController)にsearchDisplayController として紐づけられている。

//numberOfRowsInSectionを変更

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger dataCount;
    
    // ここのsearchDisplayControllerはStoryboardで紐付けされたsearchBarに自動で紐づけられている
    //検索キーワードが入力されているか判定
    if (tableView == self.searchDisplayController.searchResultsTableView) {
        // 検索中のテーブルビューはこちらで処理
        //セクションが複数ある場合はセクションごとに
        switch (section) {
            case 0:
                dataCount = self.dataSourceSearchResultsiPhone.count;
                break;
            case 1:
                dataCount = self.dataSourceSearchResultsAndroid.count;
                break;
            default:
                break;
        }
    } else {
        // 通常時のテーブルビューはこちらで処理
        switch (section) {
            case 0:
                dataCount = self.dataSourceiPhone.count;
                //もし、検索前のセルにデータを表示したくない場合はデータの数を0にする
                //dataCount = 0;
                break;
            case 1:
                dataCount = self.dataSourceAndroid.count;
                break;
            default:
                break;
        }
    }
    NSLog(@"%ld", (long)dataCount);
    return dataCount;
}

//cellForRowAtIndexPathを変更

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    // 再利用できるセルがあれば再利用する
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    if (!cell) {
        // 再利用できない場合は新規で作成
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:CellIdentifier];
    }
    
    // ここのsearchDisplayControllerはStoryboardで紐付けされたsearchBarに自動で紐づけられている
    //検索キーワードが入力されているか判定
    if (tableView == self.searchDisplayController.searchResultsTableView) {
        // 検索中のテーブルビューはこちらで処理
        //セクションが複数ある場合はセクションごとに
        switch (indexPath.section) {
            case 0:
                cell.textLabel.text = self.dataSourceSearchResultsiPhone[indexPath.row];
                break;
            case 1:
                cell.textLabel.text = self.dataSourceSearchResultsAndroid[indexPath.row];
                break;
            default:
                break;
        }
    } else {
        // 通常時のテーブルビューはこちらで処理
        switch (indexPath.section) {
            case 0: // iOS
                cell.textLabel.text = self.dataSourceiPhone[indexPath.row];
                break;
            case 1: // Android
                cell.textLabel.text = self.dataSourceAndroid[indexPath.row];
                break;
            default:
                break;
        }
    }
    
    return cell;
}

4.データを絞り込む

// 検索バーに入力された文字列を引数に、絞り込みをかけます
- (void)filterContainsWithSearchText:(NSString *)searchText
{
    //CONTAINS右辺値が含まれているか,cは大文字小文字の区別なしオプション
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@", searchText];
    
    self.dataSourceSearchResultsiPhone = [self.dataSourceiPhone filteredArrayUsingPredicate:predicate];
    self.dataSourceSearchResultsAndroid = [self.dataSourceAndroid filteredArrayUsingPredicate:predicate];
}

NSPredicate の詳細はこちら

//検索バーの文字を編集する度に呼ばれるデリゲートメソッド

- (BOOL)searchDisplayController:controller shouldReloadTableForSearchString:(NSString *)searchString
{
    // 検索バーに入力された文字列を引数に、絞り込みをかけます
    [self filterContainsWithSearchText:searchString];
    
    // YESを返すとテーブルビューがリロードされます。
    // リロードすることでdataSourceSearchResultsiPhoneとdataSourceSearchResultsAndroidからテーブルビューを表示します
    return YES;
}

//セルが選択されたら

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     
    NSLog(@"セルが選択されました");
    
}

↑これで、1度目のタップでうまく反応しない場合は、didSelectRowAtIndexPath が didDeselectRowAtIndexPath になっていないか確認すること。

参考ページ

関連記事

タイトルとURLをコピーしました