Xibを使ったUITableViewCellのカスタマイズに毎回少し躓いているのでまとめてみたいと思います。
具体的出来上がりはこのような形になります。
作成手順は次の通りです。カスタマイズ・セルを利用するテーブルビューを作成するところから始めます。
基本
- UITableViewControllerを継承したサブクラスを作成する(TableViewController.h/.mとする)。
-
UITableViewCellを継承したサブクラスを作成する(CustomCell.h/.mとする)。
-
空のXibファイルを作成する(CustomCell.xibとする)。
-
CustomCell.xibのClassをCustomCellに変更する(デフォルトはUITableViewCell)。
- CustomCell.xibのIdentifierに値を指定する(CustomCellとする。この部分はセルの再利用の際に必要になる)
- CustomCell.xibのFile’s OwnerをTableViewControllerに変更する(デフォルトはNSObject)
- CustomCellにカスタムの画面構成要素を配置し、IBOutletで接続する。
- TableViewControllerにIBOutletでCustomCellを参照するプロパティをassignで作る。
@property (nonatomic, assign) IBOutlet MZRCustomCell *customCell;
ここでTableViewControllerにIBOutletを付ける理由がわからないという人が多いと思われます。僕もそうでした。ここで接続している理由は、
- CustomCell.xibのFile’s OwnerのIBOutletにCustomCellを接続する。
- TableViewControllerにCustomCellをインポートし、cellForRowAtIndexPath:メソッドを下記のようにオーバライドする。
static NSString *CellIdentifier = @"MZRCustomCell"; MZRCustomCell *cell = (MZRCustomCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { [[NSBundle mainBundle] loadNibNamed:@"MZRCustomCell" owner:self options:nil]; cell = self.customCell; self.customCell = nil; } // Usually, you set the cell with the data from a custom array or a model manager. // This is just a sample, so I directly set the content here. UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"%d.png",indexPath.row]]; NSString *labelStr = [NSString stringWithFormat:@"This is the cell No.%d",indexPath.row]; NSString *textViewStr = [NSString stringWithFormat:@"No.%d's cell contents. 2*%d=%d",indexPath.row,indexPath.row,2*indexPath.row]; NSString *buttonStr = @"button"; cell.imageView.image = image; cell.titleLabel.text = labelStr; cell.textView.text = textViewStr; cell.button1.titleLabel.text = buttonStr; return cell;
ここでカスタムセルに表示する値を決めるが、サンプルなので適当に直打ちしている。本来ならば、ここはアレーを使ったり、データマネジャーから値を持ってきていれるのがよいと思う。それに関しては後ほど。
- あとはテーブルビュー・データソースのセクション数、セクションに含まれるセル数を返すように実装し、viewDidLoadでtableViewの見た目を設定する。今回は、以下のようにセルのサイズに合わせて修正した。
self.tableView.rowHeight = 80;
TableViewでセルを自分で作る(カスタムセル)苦闘メモ – Story not found…を主に参考にしました。不明点があった場合こちらも参考にしてください。
カスタムセルのボタン
カスタムセルにボタンをつけます。アクションの管理はビューコントローラが行うのが好ましいと思いますので、TableViewControllerにIBActionのメソッドを加えます。
- (IBAction)didTouchButton:(id)sender event:(UIEvent*)event { UITouch* touch = [[event allTouches] anyObject]; CGPoint p = [touch locationInView:self.tableView]; NSIndexPath* indexPath = [self.tableView indexPathForRowAtPoint:p]; NSLog(@"%@", indexPath); }
これをカスタムセルのボタンとつなげます。
[iOS] UINib を使ったカスタム UITableViewCell の作り方(その3)ボタンの処理[改良版]を参考にしました。この処理は今までセルにindexPathを持たせたり(そうするとセルが削除されたときに再度設定しなければ行けないなど面倒)面倒なことをしていましたが、上記の方法を知ることでだいぶ楽になりました。
さらに
セルをタップして選択したときに、セルに配置したLabelやボタンなどの状態も変更するとより自然になります。
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated { [super setHighlighted:highlighted animated:animated]; self.leftImageView.highlighted = highlighted; self.titleLabel.highlighted = highlighted; self.button1.highlighted = highlighted; } - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; self.titleLabel.highlighted = selected; self.button1.selected = selected; }
また、セルのupdateをするメソッドを独立させ、cell生成時とviewWillAppear:が呼ばれた際に呼ばれるようにすることで、詳細ビューなどでセルの値が変更されて戻ってきたときに適切にセルの表示内容の変更ができます。
updateを分離する。
- (void)updateCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { // Here you can update cell. This method is called from both when the cell is created // and and when viewWillAppear is called. When the user changed the cell value in detailed // view and come back to this view, the value of the cell changed appropriately. // Watch "viewWillAppear:" method. // Usually, you set the cell with the data from an array or a model manager. // This is just a sample, so I directly set the content here. // cast MZRCustomCell *customCell; customCell = (MZRCustomCell *)cell; UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"%d.png",indexPath.row]]; NSString *labelStr = [NSString stringWithFormat:@"This is the cell No.%d",indexPath.row]; NSString *textViewStr = [NSString stringWithFormat:@"No.%d's cell contents. 2*%d=%d",indexPath.row,indexPath.row,2*indexPath.row]; NSString *buttonStr = @"button"; customCell.imageView.image = image; customCell.titleLabel.text = labelStr; customCell.textView.text = textViewStr; customCell.button1.titleLabel.text = buttonStr; }
もともとあった位置には、以下の一文を入れる。
[self updateCell:cell atIndexPath:indexPath];
viewWillAppear:部分の実装
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // Deselect cell [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:animated]; // Update cells NSMutableArray *cells = [NSMutableArray arrayWithArray:[self.tableView visibleCells]]; for (UITableViewCell *cell in cells) { [self updateCell:cell atIndexPath:[self.tableView indexPathForCell:cell]]; } }
iOS開発におけるパターンによるオートマティズムを参考にしました。上記”NSMutableArray *cells = [NSMutableArray arrayWithArray:[self.tableView visibleCells]];”の部分は本書には無いのですが、[self.tableView visibleCells]で取得できるNSArray型の配列を高速列挙して値をcellの内容を変更するとエラーが出たのでMutableArray型に変換して操作しています。その他、本書にはいくつかの骨太の使えるパターンがあるので重宝しています。
ちなみに、上記の方法が適用できない状況の場合、Xibを使った別の実装方法があります。iPhoneアプリでカスタムTableViewCellの作り方 – あられねこのめもを参照すると、UIViewControllerの特性を利用した興味深い実装方法が記載されています。参考に。
終わりに
ブログの記事で助けられることが多いのでまねをして書いてみました。なかなかうまく行かないものですが。一つの記事の長さをどれくらいにすればよいか等きちんと考えなければ行けないことがまだまだ多そうです。
コードは下記Githubでご確認頂けます。今後のブログの状況によっては修正をしていくこともあります。