甘いものが好きです

iOS App開発時に感じた疑問や課題、その他の雑感などを書いていきます。

TableViewの各セル内にボタンを配置し、どのボタンがタップされたかを判別可能にする

iOS Appにおいて、TableView内の各セルにボタンを配置し、それらのボタンのうち1つがタップされたときにどのボタンがタップされたかを判別可能にしたい場合、どのような実装を行えばよいのか。簡単な実装例を1つ示す。

ボタンクラスのカスタマイズ

UIButtonクラスを継承したカスタムボタンクラス(ここでは「MyButton」と命名する)を作成し、どのセルに追加されているかの情報をボタンが保持できるようにする。

//
//  CustomButton.h
//  CellButtonTest
//
#import <UIKit/UIKit.h>

@interface MyButton : UIButton {
    NSUInteger section;
    NSUInteger row;
}

@property (nonatomic, readwrite) NSUInteger section;
@property (nonatomic, readwrite) NSUInteger row;

@end

ボタンタップ時のアクションを作成する

次に、ボタンタップ時に呼ばれるアクションメソッドをTableViewControllerに追加する。ボタンがタップされてこのアクションメソッドが呼ばれると、引数senderにはタップされたボタンを指すポインタが渡されるので、次のようにしてボタンが配置されているセルのsection, rowを取得することができる。

#import "MyButton.h"

- (IBAction)buttonDidTouchDown:(id)sender {
    
    MyButton *theButton = (MyButton *)sender;
    NSLog(@"Button[%d,%d] was pressed.", theButton.section, theButton.row);
}

各セルにボタンを追加する

最後に、各セルにボタンを追加する。ボタンの追加はTableViewControllerのtableView:cellForRowAtIndexPath:内で行う。

TableViewのセルは使い回すので、新規セルを作成したところ(dequeueReusableCellWithIdentifier:がnilを返した場合の処理)でセルにカスタムボタンを追加する。このカスタムボタンに後でsecitonとrowを設定するために、セルにボタンを追加するところではボタンのタグ値にはボタン専用の値(ここでは10000)を設定しておく。また、addTargert:action:forControlEvents:でボタンがタップされたときのアクションも設定しておく。

各セル内のボタンにそれぞれ異なるsection, rowを設定しておくために、tableView:cellForRowAtIndexPath:の最後でviewWithTag:でボタンを取得し、そのボタンにsection, rowを設定する。

- (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];
        
        MyButton *aButton = [[MyButton alloc] initWithFrame:CGRectMake(100, 4, 100, 35)];
        aButton.tag = 10000;
        aButton.backgroundColor = [UIColor blueColor];
        [aButton addTarget:aButton 
                    action:@selector(buttonDidTouchDown:) 
          forControlEvents:UIControlEventTouchDown];
        [cell addSubview:aButton];
    }
    
    MyButton *theButton = (MyButton *)[cell viewWithTag:10000];
    if (theButton) {
        theButton.section = [indexPath section];
        theButton.row = [indexPath row];
    }
    cell.textLabel.text = [NSString stringWithFormat:@"%d", [indexPath row]];
    return cell;
}

補足

  • 今回はセルの内部構成が極めてシンプルだったが、セルの内部構成がより複雑になる場合にはカスタムセルを使う方が良いかもしれない。上の実装例では記述を簡略化するために、カスタムセルを使わず、tableView:cellForRowAtIndexPath:の中で直にセルの内容を設定してしまっている。
  • この記事にあるとおり、dequeueReusableCellWithIdentifier:で取得したセルの内部にオブジェクトを追加すると、セルの再利用時に問題となってしまうので要注意。