忍者ブログ

[PR]

2024年03月19日
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

UITableViewControllerはどこへ行った?

2009年06月22日
iPhone SDK 3.0では新規にファイルを作る場合の種類の選択方法が今までとは少し変更が加わっています。以前は開いたウインドウの左側にあるリストからiPhone OSのCocoa Touch Classを選び、右側に表示される多数のアイコンの中からUITableViewController subclassを選んでいました。しかしiPhone SDK 3.0ではアイコンはObjective-C classとObjective-C test case class と UIViewController subclassの3つしか表示されません。

UIViewControllerのアイコンだけがそのまま残されているため、ぱっと見で今までの選択用アイコンがインストールに失敗して消えてしまったかのように思えます。私はしばらくの間これで大丈夫なのかと不安になりました。でもこれでは別に異常ではなく、正常な状態のようです。ではUITableViewController subclassやUITableViewCell subclassなどはどこに行ってしまったのでしょうか。
PR
つづきはこちら "UITableViewControllerはどこへ行った?"

iPhone OS 3.0 におけるセルの変更点

2009年06月21日
3.0になったことによる、UITableView関連での変更点はUITableViewCellの変更だけのようです。これまではセルそのものがUILabelのようにtextを持っていたのですが、3.0以降ではセルは2種類のラベルを持ちます。textLabelとdetailTextLabelです。従って、3.0専用にアプリケーションを作る場合には、今までとは少しだけセルへのテキストの渡し方が変わります。これまで「セルの変数名.text」だったのが、「セルの変数名.textLabel.text」や「セルの変数名.detailTextLabel.text」になります。また、セルの作成時の宣言がフレームからスタイルに変更されています。スタイルは4種類の中から選びます。文章で書いていても分かりにくいので、スクリーンショットを掲載します。

cell3.PNG左にある画像が4種類のスタイルをそれぞれ適応したセルです。一番上がUITableViewCellStyleDefaultです。textLabelだけ表示される、今までに近い形の表示です。次がUITableViewCellStyleValue1です。タイトルと値を表示しますが、セルの幅を全て使う表示になります。その次がUITableViewCellStyleValue2です。タイトルと値が寄せて表示されます。最後がUITableViewCellStyleSubtitleです。2つのラベルが上下に並んで表示されます。ちなみに画像もこれまでは「セルの変数名.image」でしたが、「セルの変数名.imageView.image」となります。画像はスタイルがUITableViewCellStyleDefaultとUITableViewCellStyleSubtitleの場合だけラベルの左に挿入されます。


以下は上のスクリーンショットを撮影するのに使ったコードです。Navigation-based Applicationを選んでRootViewController.mの該当箇所をこのコードのように書き換えれば同じように表示されるはずです。Xcodeにそのままコピー&ペーストできるようにレイアウトはいじっていません。なお、これだけ少数のセルなので表示は全く問題ないはずですが、一応複数種のセルを使っていることもあるので、以前の記事『数種類のセルを使用してもスクロール速度の低下を極力減らす方法』と同じ書き方にしてあります。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return 4;

}

 

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {    

// 行ごとに表示を変更します。

switch (indexPath.row) {

case 0: {

UITableViewCell *cellDefault = [tableView dequeueReusableCellWithIdentifier:@"cellDefault"];

if (cellDefault == nil) {

cellDefault = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cellDefault"] autorelease];

}

cellDefault.textLabel.text = @"textLabel";

return cellDefault;

}

break;

case 1: {

UITableViewCell *cellValue1 = [tableView dequeueReusableCellWithIdentifier:@"cellValue1"];

if (cellValue1 == nil) {

cellValue1 = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"cellValue1"] autorelease];

}

cellValue1.textLabel.text = @"textLabel";

cellValue1.detailTextLabel.text = @"detailTextLabel";

return cellValue1;

}

break;

case 2: {

UITableViewCell *cellValue2 = [tableView dequeueReusableCellWithIdentifier:@"cellValue2"];

if (cellValue2 == nil) {

cellValue2 = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:@"cellValue2"] autorelease];

}

cellValue2.textLabel.text = @"textLabel";

cellValue2.detailTextLabel.text = @"detailTextLabel";

return cellValue2;

}

break;

case 3: {

UITableViewCell *cellSubtitle = [tableView dequeueReusableCellWithIdentifier:@"cellSubtitle"];

if (cellSubtitle == nil) {

cellSubtitle = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cellSubtitle"] autorelease];

}

cellSubtitle.textLabel.text = @"textLabel";

cellSubtitle.detailTextLabel.text = @"detailTextLabel";

return cellSubtitle;

}

break;

}

    UITableViewCell *cellDefault = [tableView dequeueReusableCellWithIdentifier:@"cellDefault"];

    if (cellDefault == nil) {

        cellDefault = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cellDefault"] autorelease];

    }

return cellDefault;

}

 
2010年5月23日補足:
最後の実際に使われない部分はreturn nil;でもセル数との整合性が失われていなければ全く問題ないのですが、例えばtableView:numberOfRoawsInSection:で戻る値を4から5に変更するとここで空のオブジェクトが返り、結果としてアプリケーションが強制終了されてしまいます。そのような場合でも一応セルオブジェクトを返す事によりアプリケーション全体が落ちるのを避けられるようにしているため、このような一見すると面倒な表記になっています。

数種類のセルを使用してもスクロール速度の低下を極力減らす方法

2009年05月19日
以前の記事『テーブルのセルへの参照を2書類以上混在させる』で数種類のセルを混在させる方法を書きましたが、あの方法には実は大きな欠点がありました。その欠点はテーブルが画面に納まりきる場合には全く問題にならないのですが、テーブルのセル数が多くスクロールさせる必要がある場合に大問題になります。そう、スクロール速度がとても遅くなるのです。

最近Tiny3Dで各基本図形の詳細設定をする画面のテーブルスクロールを少しでも高速にしようといろいろ試行錯誤していてわかったのですが、セルの再利用を行えるかの判定部分の処理はかなり重たいようです。スクロール時には画面に表示するセルが次々変化するため、その度に行われるセル再利用判定がスクロールの邪魔をしてしまっていたのです。

つまり、それらの判定を必要最小限に行うようにした方がテーブルのスクロールを高速に出来ます。そのためには、たとえ複数回の重複部分が発生してコードの行数が多くなろうと、セルのセクションや行での分岐後で、そのとき必要なセルに限定した再利用判定をした方が良いということになります。以前のコードをテーブルのスクロール速度も考慮したものに変更すると、以下のように書き換えられます。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

switch (indexPath.row) {

case 0: {

HorizontalSwitchTableViewCell *sCell =
  (HorizontalSwitchTableViewCell *)[tableView
 
dequeueReusableCellWithIdentifier:@"HorizontalSwitchTableViewCell"];

if (sCell == nil) {

sCell = [[[HorizontalSwitchTableViewCell alloc]
  initWithFrame:CGRectZero
reuseIdentifier:@"HorizontalSwitchTableViewCell"]autorelease];

}

sCell.selectionStyle = UITableViewCellSelectionStyleNone;

sCell.myDictionary = self.myDictionary;

sCell.editedKey = @"display";

[sCell.switchCtl setOn:[[myDictionaryobjectForKey:@"display"]boolValue]
animated:NO];

 

sCell.nomalLabel.text = NSLocalizedString(@"Display",@"");

return sCell;

break;

}

case 1: {

UITableViewCell *cell = [tableView
  dequeueReusableCellWithIdentifier:@"Cell"];

if (cell == nil) {

cell = [[[UITableViewCell allocinitWithFrame:CGRectZero
reuseIdentifier:@"Cell"autorelease];

}

cell.font = [UIFont systemFontOfSize:20];

cell.text = [NSString stringWithFormat:NSLocalizedString(@"Name
%@",@""),[myDictionary objectForKey:@"name"]];

return cell;

break;

}

case 2: {

HorizontalPositionTableViewCell *pCell =
  (HorizontalPositionTableViewCell *)[tableView
 
dequeueReusableCellWithIdentifier:@"HorizontalPositionTableViewCell"];

if (pCell == nil) {

pCell = [[[HorizontalPositionTableViewCell alloc]
  initWithFrame:CGRectZero
reuseIdentifier:@"HorizontalPositionTableViewCell"]  autorelease];

}

pCell.enabledSetting = YES;

pCell.titleLabel.text = NSLocalizedString(@"Center",@"");

pCell.leftValueLabel.text = [NSStringstringWithFormat:@"%f",[[myDictionary
  objectForKey:@"centerx"]floatValue]];

pCell.centerValueLabel.text = [NSString
  stringWithFormat:@"%f",[[myDictionary objectForKey:@"centery"]floatValue]];

pCell.rightValueLabel.text = [NSStringstringWithFormat:@"%f",[[myDictionary
  objectForKey:@"centerz"]floatValue]];

return pCell;

break;

}

case 3: {

HorizontalPositionTableViewCell *pCell = 
(HorizontalPositionTableViewCell *)[tableView
 
dequeueReusableCellWithIdentifier:@"HorizontalPositionTableViewCell"];

if (pCell == nil) {

pCell = [[[HorizontalPositionTableViewCell alloc]
  initWithFrame:CGRectZero
reuseIdentifier:@"HorizontalPositionTableViewCell"]autorelease];

}

pCell.enabledSetting = YES;

pCell.titleLabel.text = NSLocalizedString(@"Rotate",@"");

pCell.leftValueLabel.text = [NSString
stringWithFormat:@"%f",[[myDictionary objectForKey:@"rotatex"]floatValue]];

pCell.centerValueLabel.text = [NSString
stringWithFormat:@"%f",[[myDictionary objectForKey:@"rotatey"]floatValue]];

pCell.rightValueLabel.text = [NSString
stringWithFormat:@"%f",[[myDictionary objectForKey:@"rotatez"]floatValue]];

return pCell;

break;

}

case 4: {

UITableViewCell *cell = [tableView
  dequeueReusableCellWithIdentifier:@"Cell"];

if (cell == nil) {

cell = [[[UITableViewCell allocinitWithFrame:CGRectZero
reuseIdentifier:@"Cell"autorelease];

}

cell.font = [UIFont systemFontOfSize:20];

if ([myDictionary objectForKey:@"magnification"] == nil) {

[myDictionary setObject:[NSNumber numberWithFloat:1]
  forKey:@"magnification"];

}

cell.text = [NSStringstringWithFormat:NSLocalizedString(@"Magnification:
  %f",@""),[[myDictionary objectForKey:@"magnification"]floatValue]];

return cell;

break;

}

}

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];

    if (cell == nil) {

        cell = [[[UITableViewCell allocinitWithFrame:CGRectZero reuseIdentifier:@"Cell"]
        autorelease];

    }

    return cell;

}

 
なんだかぱっと見では複数回同じコードが見えていて簡潔でなくなったように見えます。しかし実機を用いてスクロールさせた場合の速度は、このように記述しておいた方が明らかに速くなります。なお前回も書きましたが、最後の部分は標準設定でビルドしたときの警告回避用なので、実際使われる事はありません。

テーブルのセルへの参照を2種類以上混在させる

2009年02月15日
アップルから提供されてるテーブル関連のサンプルを見ていると、cellForRowAtIndexPathで返すセルは必ずといっていいほど1種類に限定されています。そのため、セルを何種類も作っては駄目なのかと思いがちですが、別にそんな制限はありません。数種類のセルへの参照を作っておき、列ごとに返すセルを変える事もできます。ちょっと長いのですが一例として、Tiny3Dでオブジェクトやパートを編集するときに開くテーブルでのcellForRowAtIndexPathを示します。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Cell"]autorelease];

}

PositionTableViewCell *pCell = (PositionTableViewCell *)[tableView

dequeueReusableCellWithIdentifier:@"PositionTableViewCell"];

if (pCell == nil) {

pCell = [[[PositionTableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"PositionTableViewCell"] autorelease];

}

SwitchTableViewCell *sCell = (SwitchTableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"SwitchTableViewCell"];

if (sCell == nil) {

sCell = [[[SwitchTableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"SwitchTableViewCell"] autorelease];

}
switch (indexPath.row) {
case 0: {

sCell.selectionStyle = UITableViewCellSelectionStyleNone;

sCell.myDictionary = self.myDictionary;

sCell.editedKey = @"display";

[sCell.switchCtl setOn:[[myDictionary objectForKey:@"display"] boolValue]
animated:NO];

sCell.nomalLabel.text = NSLocalizedString(@"Display",@"");

return sCell;

break;

}

case 1: {

cell.font = [UIFont systemFontOfSize:20];

cell.text = [NSString stringWithFormat:NSLocalizedString(@"Name: 
%@"@""),[myDictionary objectForKey:@"name"]];

return cell;

break;

}

case 2: {

pCell.enabledSetting = YES;

pCell.titleLabel.text = NSLocalizedString(@"Center",@"");

pCell.leftTitleLabel.text = @"X";

pCell.leftValueLabel.text = [NSString stringWithFormat:@"%f",
[[
myDictionary objectForKey:@"centerx"]floatValue]];

pCell.centerTitleLabel.text = @"Y";

pCell.centerValueLabel.text = [NSString stringWithFormat:@"%f",
[[
myDictionary objectForKey:@"centery"] floatValue]];

pCell.rightTitleLabel.text = @"Z";

pCell.rightValueLabel.text = [NSString stringWithFormat:@"%f",
[[myDictionary objectForKey:@"centerz"] floatValue]];

return pCell;

break;

}

case 3: {

pCell.enabledSetting = YES;

pCell.titleLabel.text = NSLocalizedString(@"Rotate",@"");

pCell.leftTitleLabel.text = @"X";

pCell.leftValueLabel.text = [NSString stringWithFormat:@"%f",
[[
myDictionary objectForKey:@"rotatex"] floatValue]];

pCell.centerTitleLabel.text = @"Y";

pCell.centerValueLabel.text = [NSString stringWithFormat:@"%f",
[[
myDictionary objectForKey:@"rotatey"] floatValue]];

pCell.rightTitleLabel.text = @"Z";

pCell.rightValueLabel.text = [NSString stringWithFormat:@"%f",
[[
myDictionary objectForKey:@"rotatez"] floatValue]];

return pCell;

break;

}

case 4: {

cell.font = [UIFont systemFontOfSize:20];

if ([myDictionary objectForKey:@"magnification"] == nil) {

[myDictionary setObject:[NSNumber numberWithFloat:1]
forKey:@"magnification"];

}

cell.text = [NSString stringWithFormatNSLocalizedString (
@"Magnification: %f",@""),[[myDictionary objectForKey:
@"magnification"]floatValue]];

return cell;

break;

}

}

    return cell;

}

 
こんな感じで、分岐後にreturnでさっさとセルへの参照を返してしまえば良いのですね。ただ、あまりたくさんのセル参照を用意すると、スクロール時のパフォーマンスが低下することも考えられますので注意が必要です。なお、一番最後のreturnはビルド時に警告が出るのを防ぐためのもので、実際にこれでセルを返す事はありません。






編集モード中もセルの選択を可能にする方法

2009年02月11日
アップル提供のサンプルEditableDetailViewでは編集モード中も選択が有効になっています。そのため、編集モードでセルをタップすると内容の変更ができます。ところが、普通にInterfaceBuilderを使ってTableViewを作成しても、編集モード中は選択が有効にならず、タップしても無反応になってしまいます。どこかに必ず編集モード中でも選択を有効にする設定があるはずと思い探していたのですが、なかなか見つかりませんでした。それが最近になってようやく発見できました。

それはInterfaceBuilderのAttributes InspecterのTable Viewの項目にある、Allow Selection While Editingにチェックを入れることでした。灯台もと暗しとはまさにこの事ですね。

これで編集モード中にセルをタップするとdidSelectRowAtIndexPathがメッセージを受信するようになりますので、self.editingのBOOL値で分岐させれば通常モードと別の動きをさせることができます。この発見のおかげで、Tiny3Dのバージョン1.2.0以降では、いちいち編集画面を開かなくてもオブジェクトの表示・非表示の切り替えができるようになりました。(まだ他にも手を加えているのでアップまでにはしばらく時間がかかります。ユーザーの皆様、気長にお待ちください。)他にも気づいていない設定がいろいろありそうです(汗)。

一つ手前の階層のビューへ戻る

2009年01月22日
 前回の記事で、上の階層に戻るのにナビゲーションバーのバックボタンを使いました。でも、オプションの選択等で項目を選んだらすぐに元のテーブルに戻りたいこともあります。そんな場合には下記の方法を使います。先ほどのコードをそのまま利用して話を進めます。

  1. AnotherViewController.mでnumberOfRowsInSectionのreturnを0から1にする。
  2. 前回と同様、cellForRowAtIndexPathで「cell.text = @"back";」をreturnの上の行に追加する。
  3. didSelectRowAtIndexPathに「[self.navigationController popViewControllerAnimated:YES];」を追加する。

ビルドして進行すると、前回と同様にテーブルが表示され、項目のタップで下の階層に移動します。ここでテーブルの項目であるbackをタップすると、上の階層のテーブルに戻ります。実際のプログラムでの応用ではdidSelectRowAtIndexPath内で戻る前に選択内容を変数に保存したり選択に伴う処理を入れれば良いわけです。なお、これらのアクションをIBActionメソッドとしておきボタンのタップ等で実行するようにすれば、テーブルではないビューでも同様の動作を行わせることができます。

お手軽ナビゲーション方法

2009年01月22日
いままでに書いた記事で、テーブルの大まかな書式設定は理解できたのではないかと思います。そこで今回は、テーブルの項目を選択したら、右側にスクロールして下層に移動する方法を説明します。全部説明すると膨大な文量になってしまうので、ざっくりとポイントだけ説明します。

  1. Navigation-Based Applicationのプロジェクトを新規作成します。プロジェクト名は何でも構いません。
  2. RootViewController.mで、viewWillAppearのコメントアウトを外し、中に「self.title = @"abc";」を追加します。中括弧の中であれば行の順番は気にしなくて大丈夫です。
  3. numberOfRowsInSectionのreturnを0から1に変更します。
  4. その下のcellForRowAtIndexPathの「return cell;」とある行の上にある空白の行に、「cell.text = @"123";」と記述します。
  5. 左側のリストのClassにTableViewControllerを新規ファイルで追加します。名前はAnotherViewControllerとします。
  6. RootViewController.mで冒頭の「@implementation」の上の行に「#import "AnotherViewController.h"」を追加します。
  7. didSelectRowAtIndexPath内のコメントのうち下3行の「//」を消します。
  8. 左側のリストのResourcesにView XIBを新規ファイルで追加します。名前はAnotherViewとします。
  9. AnotherView.xibを開き、File's OwnerのクラスをIdentity InspectorでAnotherViewControllerに変更します。
  10. ライブラリからTableViewをドラッグ&ドロップで追加し、もともとあるViewは消します。追加したTableViewのdataSourceとdelegateをFile's Ownerに接続します。File's OwnerのviewをTable Viewに接続します。

ビルドして進行すると、タイトルがabcという名前のテーブルが表示されます。123という項目をタップすると、右にスクロールして別のテーブルが表示されます。abcと書いてあるボタンを押すと、左にスクロールして元のテーブルに戻ります。


各セルにアクセサリーを設定する

2009年01月19日
テーブルの各セルの右側には、アクセサリーと呼ばれるパーツを配置することができます。これを実現するのに使うのがaccessoryTypeForRowWithIndexPath:です。とりあえず例によって実際のコードから引っ張ってきましたので、まずはそれを見てみましょう。

- (UITableViewCellAccessoryType)tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath

{

return (self.editing) ? UITableViewCellAccessoryDetailDisclosureButton :

UITableViewCellAccessoryDisclosureIndicator;

}

 
さて、このコードでは編集ボタンが押されて編集状態にあるか、そうでないかでアクセサリーを切り替えています。編集モードではディテールディスクロージャーボタンという、青丸に>記号が入ったようなボタンが表示されます。編集モードでない時はディスクロージャーインジケーターという、灰色の>が表示されます。ちなみにアクセサリーの種類は以下の4種類です。

UITableViewCellAccessoryNone:何も表示しません。
UITableViewCellAccessoryDisclosureIndicator:灰色の>を表示します。
UITableViewCellAccessoryDetailDisclosureButton:青丸に>記号が入ったボタンが表示されます。
UITableViewCellAccessoryCheckmark:チェックマークを表示します。

なお、以前の記事「前回起動時の選択項目にチェックマークを付ける」に書いたように、セルのプロパティ(accessoryType)を直接設定する方法もあります。この命令は初期設定用として、表示してからの動的な変更にaccessoryTypeを使うようにすると良いでしょう。

2009年11月7日追記
iPhone OS 3.0以降では上記のtableView:accessoryTypeForRowWithIndexPath:は使用できません。今のところは一応動きますが、ビルド時に警告が出るようになります。この場合はtableView:cellForRowAtIndexPath:内でUITableViewCellのプロパティaccessoryTypeとeditingAccessoryTypeを用いて通常及び編集時のアクセサリーを設定すると良いでしょう。

 | HOME | 次のページ »