[iOS] チェックボックスをつけたUITableViewCellをつくる

2012/07/1

こんにちは。きんくまです。

チェックボックスをつけたUITableViewCellを作るのにちょっとハマったのでメモです。

checkbox_cap1

UITableViewCellのサブクラスでチェックボックスを作るところまではいいのですが、
このセルには情報を保存しておけないのでした。

UITableViewDataSourceの– tableView:cellForRowAtIndexPath: で画面から見えなくなったセルを破棄したり、これから見えるセルを自動作成してくれます。
それでセルを作成する際に、dequeueReusableCellWithIdentifierでキャッシュされているセルを使う場合があります。

このキャッシュがくせ者で、サブクラスの中に情報を埋め込んでいても、予想とは違うキャッシュされたセルがひっぱられることがあります。
例えばindexPathのデータをセルにいれても、セルとindexPathが1対1に対応されないとか。
たくさんの情報を入れてスクロールしてみるとよくわかりますです。

で調べたところApple のサンプルコードが出てきました。
>> Accessory
(何故か昨日までは見られたのに、今は見られないという、、)

いろいろと考えてみたところ、ポイントとしては、

1) セルの方には情報をもたせず、親の方にもたせる
2) UITableViewのindexPathForRowAtPoint: を使ってタッチされたセルを特定
3) tableView:cellForRowAtIndexPath:のときに保存しておいたデータをもとにセルの見た目を設定する

という感じでしょうか。

これをふまえて、作成したのが以下のコードです。
参考までにプロジェクトデータもアップしました。

※2012/07/22 delegateのプロパティ属性を修正しました
>> CellWithCheckbox

KKTableViewController.h

#import <UIKit/UIKit.h>
#import "KKCellWithCheckbox.h"

@interface KKTableViewController : UITableViewController<KKCellWithCheckboxDelegate>
{
    NSArray *_cellDataArray;
}

@end

KKTableViewController.m

#import "KKTableViewController.h"
#import "KKCellWithCheckbox.h"

@interface KKCellData:NSObject {
    NSString *_cellName;
    BOOL _isChecked;
}
@property(nonatomic, retain) NSString *cellName;
@property(nonatomic, assign) BOOL isChecked;
@end

@implementation KKCellData
@synthesize cellName = _cellName;
@synthesize isChecked = _isChecked;

- (void)dealloc
{
    [_cellName release];
    [super dealloc];
}
@end



@interface KKTableViewController ()

@end

@implementation KKTableViewController

- (id)init
{
    self = [super init];
    if (self){
        NSMutableArray *tmpArr = [[NSMutableArray alloc] init];
        for (int i = 0; i < 100; i++){
            NSString *cellName = [NSString stringWithFormat:@"row %d", i];
            KKCellData *cellData = [[KKCellData alloc] init];
            cellData.cellName = cellName;
            cellData.isChecked = NO;
            [tmpArr addObject:cellData];
            [cellData release];
        }
        _cellDataArray = [[NSArray alloc] initWithArray:tmpArr];
        [tmpArr release];
    }
    return self;
}

- (void)dealloc
{
    [_cellDataArray release];
    [super dealloc];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"KKTableViewController";
    KKCellWithCheckbox *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if(cell == nil){
        cell = [[[KKCellWithCheckbox alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
    }
    cell.delegate = self;
    [cell setCheckboxState:[(KKCellData *)[_cellDataArray objectAtIndex:indexPath.row] isChecked]];
    KKCellData *cellData = [_cellDataArray objectAtIndex:indexPath.row];
    [cell setCellName:cellData.cellName];

    return cell;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [_cellDataArray count];
}

- (void)cell:(KKCellWithCheckbox *)cell checkboxTappedEvent:(UITouch *)touch
{
    CGPoint touchPt = [touch locationInView:self.view];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:touchPt];
    KKCellData *cellData = [_cellDataArray objectAtIndex:indexPath.row];
    cellData.isChecked = !cellData.isChecked;
    [cell setCheckboxState:cellData.isChecked];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"row %d is selected", indexPath.row);
}

@end

KKCellWithCheckbox.h

#import <UIKit/UIKit.h>

@class KKCellWithCheckbox;

@protocol KKCellWithCheckboxDelegate<NSObject>
@optional
-(void)cell:(KKCellWithCheckbox *)cell checkboxTappedEvent:(UITouch *)touch;
@end

@interface KKCellWithCheckbox : UITableViewCell{
    UILabel *_cellNameLabel;
    UIButton *_checkboxButton;
    UIImage *_checkedImage;
    UIImage *_uncheckedImage;
    id<KKCellWithCheckboxDelegate> _delegate;
}
@property(nonatomic, assign) id <KKCellWithCheckboxDelegate> delegate;
- (void)setCellName:(NSString *)aCellName;
- (void)setCheckboxState:(BOOL)isCheck;
@end

KKCellWithCheckbox.m


#import "KKCellWithCheckbox.h"

@interface KKCellWithCheckbox()
- (void)checkboxTapped:(id)sender event:(id)event;
@end

@implementation KKCellWithCheckbox
@synthesize delegate = _delegate;

- (void)dealloc
{
    [_cellNameLabel release];
    [_checkedImage release];
    [_uncheckedImage release];
    [_checkboxButton release];
    [super dealloc];
}

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        _checkboxButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
        NSString *checkedImgPath = [[NSBundle mainBundle] pathForResource:@"checkedmark" ofType:@"png"];
        NSString *uncheckedImgPath = [[NSBundle mainBundle] pathForResource:@"uncheckmark" ofType:@"png"];
        _checkedImage = [[UIImage alloc] initWithContentsOfFile:checkedImgPath];
        _uncheckedImage = [[UIImage alloc] initWithContentsOfFile:uncheckedImgPath];
        [_checkboxButton setImage:_uncheckedImage forState:UIControlStateNormal];
        _checkboxButton.frame = CGRectMake(0, 0, 44, 44);
        [_checkboxButton addTarget:self
                            action:@selector(checkboxTapped:event:)
                  forControlEvents:UIControlEventTouchUpInside];
        [self addSubview:_checkboxButton];

        _cellNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(60, 11, 250, 20)];
        _cellNameLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:16];
        [self addSubview:_cellNameLabel];
    }
    return self;
}

- (void)setCellName:(NSString *)aCellName
{
    _cellNameLabel.text = aCellName;
}

- (void)checkboxTapped:(id)sender event:(id)event
{
    NSSet *set = [event allTouches];
    UITouch *touch = [set anyObject];
    if (touch && [_delegate respondsToSelector:@selector(cell:checkboxTappedEvent:)]){
        [_delegate cell:self checkboxTappedEvent:touch];
    }
}

- (void)setCheckboxState:(BOOL)isCheck
{
    if(isCheck){
        [_checkboxButton setImage:_checkedImage forState:UIControlStateNormal];
    }else{
        [_checkboxButton setImage:_uncheckedImage forState:UIControlStateNormal];
    }
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    [super setSelected:selected animated:animated];
    if (selected){
        _cellNameLabel.textColor = [UIColor whiteColor];
    }else{
        _cellNameLabel.textColor = [UIColor blackColor];
    }
}

@end
LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

LINEスタンプ作りました!
毎日使える。とぼけたウサギ。LINEスタンプ販売中! 毎日使える。とぼけたウサギ

ページトップへ戻る