Hatena::Groupiphone-dev

iPhoneアプリ開発まっしぐら★ このページをアンテナに追加 RSSフィード

引っ越し後の日記はコチラです

tokoromのその他の日記

2010-02-14

UITableViewCellにコントロールを追加する方法の訂正

| 13:40 | はてなブックマーク -  UITableViewCellにコントロールを追加する方法の訂正 - iPhoneアプリ開発まっしぐら★

UIKit本追加/補足 > UIKit本の内容自体への補足/訂正 > UITableViewCellにコントロールを追加する方法の訂正


※Twitterで @ytka さんにご指摘いただいての訂正です。@ytkaさん、ありがとうございました!

UIKit本の 9.4.8 セルにコントロールを追加 (P.319) でセルのカスタマイズについて以下のようなコードを紹介しています。


■誤ったコード

- (UITableViewCell*)tableView:(UITableView*)tableView
  cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
  static NSString* identifier = @"basis-cell";
  UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier];
  if ( nil == cell ) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                  reuseIdentifier:identifier];
    [cell autorelease];
  }
  cell.textLabel.text =
    [[dataSource_ objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
  switch ( indexPath.section ) {
    case 0: //< セルにUIImageViewを追加
      [cell.contentView addSubview:[self imageViewForCell:cell withFileName:@"Samurai.png"]];
      break;
    case 1: //< セルにUISwitchを追加
      [cell.contentView addSubview:[self switchForCell:cell]];
      break;
    case 2: //< セルにUISliderを追加
      [cell.contentView addSubview:[self sliderForCell:cell]];
      break;
    default:
      break;
  }
  return cell;
}

これは、図.Bのようにセルに様々なコントロールを追加するためのサンプルコードです。

図.B

f:id:tokorom:20100214133638p:image


これは誤ったコードで、この例のようにセルの再利用が発生しにくいケースでこそ問題は顕在化しませんが、セルの再利用が発生したときに、その再利用されたセルに次々とコントロールがaddSubviewされて画面がぐちゃぐちゃになってしまいます。

このように、dequeueReusableCellWithIdentifier:メソッドで取得したセルに対してコントロールを追加することは基本的にやってはいけません。dequeueReusableCellWithIdentifier:メソッドで取得したセルに対しては、値の変更のみ行うのが正しいやりかたです。

修正したサンプルコードを以下に添付させていただきます。

ここではセルを作成する流れがわかる箇所のみ抜粋しています。全てのコードは、

に置いてありますので、全体が必要な場合にはお手数ですがこちらをご参照ください。

UIKit本をお持ちのかたは、Subversionから更新していただくことで、この修正を含めた全サンプルコードをダウンロードいただけます。

□修正後のサンプルコード

- (UITableViewCell*)tableView:(UITableView*)tableView
  cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
  // カスタムセルの種類によって再利用のためのIDを変えること
  static const id identifiers[3] = { @"image-cell", @"switch-cell", @"slider-cell" };
  NSString* identifier = identifiers[ indexPath.section ];
  UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier];
  if ( nil == cell ) {
    // セルにコントロールを追加する場合は、セルの作成時に行う
    // ここでは各カスタムセルごとにUITableViewCellのサブクラスを定義して使っている
    // 各サブクラスの実装は http://iphone-book-sample.googlecode.com/svn/trunk/Chapter9/TableSample/Classes/SampleForCustomizedCell.m 参照
    // なお、各initXXXメソッド内でそれぞれのセルに必要なコントロールをaddSubviewしている
    switch ( indexPath.section ) {
      case 0:
        cell = [[[CellWithImageView alloc] initWithReuseIdentifier:identifier] autorelease];
        break;
      case 1:
        cell = [[[CellWithSwitch alloc] initWithReuseIdentifier:identifier] autorelease];
        break;
      case 2:
      default:
        cell = [[[CellWithSlider alloc] initWithReuseIdentifier:identifier] autorelease];
        [(CellWithSlider*)cell setDelegate:self];
        break;
    }
  }
  // 再利用のときにも通るコードでは、各コントロールの値の変更のみ行う
  switch ( indexPath.section ) {
    case 0:
      [[cell imageView] setImage:[UIImage imageNamed:@"Samurai.png"]];
      break;
    case 2:
      {
        // UISlider付のセルなら値を設定
        NSNumber* value = [self.sliderValues objectAtIndex:indexPath.row];
        CellWithSlider* cellWithSlider = (CellWithSlider*)cell;
        cellWithSlider.slider.value = [value floatValue];
        cellWithSlider.row = indexPath.row;
      }
      break;
    default:
      break;
  }
  cell.textLabel.text =
    [[self.dataSource objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
  return cell;
}

全ての読者の皆様、至らない点があり申し訳ありませんでした。

notoroidnotoroid2010/03/29 18:12楽しく拝見させていただきました。ドロップ中に「新規追加」の場所を維持する場合、sourceIndexPath ではなく[NSIndexPath indexPathForRow:proposedDestinationIndexPath.row-1 inSection:proposedDestinationIndexPath.section] を返すのはいかがでしょう?

tokoromtokorom2010/03/29 22:21なるほど、そうすると(一番下の)新規追加のところに移動できなくても、そのすぐ上に移動することになるわけですね^^
名案ありがとうございます!

muriyarikayomuriyarikayo2011/08/03 19:36こんにちは。
画面に表示しきれない数のCellがある場合は、notoroidさんのおっしゃるように、
現在表示されているCellのindexPathを返さないと、非表示のCellのキャッシュが出来てしまいUIが崩壊します。

grxqcblnwpgrxqcblnwp2014/02/04 23:06fyixpjqipof.efw, <a href="http://www.djlxctovam.com/">slmmwekfjh</a> , [url=http://www.cjdqexhwco.com/]yvkvuhadyt[/url], http://www.rmwwdekfmq.com/ slmmwekfjh