2012年4月14日土曜日

iOS: UITableViewを使って横スクロール型画像ビュアーの実装

UITableView を横スクロールさせる画像ビュアーを作ってみましょう。
UITableView の横スクロール方法は多くの方が語られておりますが、今回はちょっとした Tips も含めて書いてみました。

UITableView は基本的に縦スクロールしか機能がないので、その View を90度回転させてやることで横スクロールを実現させます。
その際、UITableView を直接90度回転させるのではなく、UIView の上に UITableView を載せ、その UIView を回転させるのがコツです。
そうしないと UITableView のタッチ検出がうまくいかない時があるそうです。

以下のサイトを参考にしました。
UITableViewを回転して横スクロールする際のタッチのエラーと対策方法 | Zero4Racer PRO Developer's Blog

なお今回のサンプルプロジェクトは ARC を使って実装してみました。

UIViewController と、UIView、UITableView の関係は以下の図のようになっています。
赤い矢印が strong 参照で、青い矢印が weak 参照です。

UIViewController のヘッダファイルは以下のようになります。
@interface ImageViewSampleViewController : UIViewController <UITableViewDataSource,UITableViewDelegate>

@property (nonatomic,weak)IBOutlet UITableView *tableView;
@property (nonatomic,weak)IBOutlet UIView *bgView;

@end
bgView と tableView が weak 参照なのは、ルートとなる view が bgView を保持し、bgView が tableView を保持するので、あえて UIViewController がこれらを保持する必要が無いからです。
逆に保持してしまうと、dealloc メソッドを実装して、その中で bgView、tableView を明示的に解放してやらないとメモリーリークを起こしてしまいますので注意して下さい。

上記の図を参考に、InterfaceBuilder で各ビューを配置します。
デフォルトの view の上に bgView を配置し、その上に tableView を配置します。
bgView と tableView の Outlet 接続はもちろんのこと、tableView の dataSource と delegate を File's Owner(UIViewController) に設定するのを忘れないで下さい。僕はよく忘れて、いつも途方に暮れます。(苦笑)

更に、tableView の Row Height の値を 320 にしておきます。こうすることで1画面1画像の画像ビュアーになりますが、この辺は各自のデザインしだいです。
Header、Footer のサイズは、今回は使わないのでいくつでもいいです。

さて、次はいよいよ UITableView の回転です。
UIViewController の実装ファイルの、変更点、追加点のみを以下に書き出しました。
- (void)viewDidLoad
{
  [super viewDidLoad];

  // bgView の回転 (時計回りに90°)
  // -90°にしないのにはわけがある!
  CGRect originalFrame = self.bgView.frame;
  self.bgView.center = CGPointMake(320/2,480/2);
  self.bgView.transform = CGAffineTransformMakeRotation(M_PI * 90 / 180.0f);
  self.bgView.frame = originalFrame;

  // ページ単位でスクロールをかっちり止めるにはYES
  self.tableView.pagingEnabled = YES;

  // UITableViewも親につられて時計回りに90°回転しているので、一番下のrowが左端になる。
  // よって一番下のrowを最初に表示させる。
  NSIndexPath* indexPath = [NSIndexPath indexPathForRow:IMAGE_MAX-1 inSection:0];  
  [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];

  [self.tableView reloadData];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
   // Return the number of sections.
   return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
   // 画像の数を返す
   return IMAGE_MAX;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   static NSString *CellIdentifier = @"ImageCell";

  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
  }

  NSString *cellImageName = [NSString stringWithFormat:@"Sample%d.png",IMAGE_MAX-indexPath.row];
  cell.imageView.image = [UIImage imageNamed:cellImageName];
  // UITableViewが時計回りに90°回転しているので、cell のコンテンツビューを 反時計回りに90°回転させる。
  cell.contentView.transform = CGAffineTransformMakeRotation(M_PI * (-90) / 180.0f);
  return cell;
}

#pragma mark - Table view delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  NSLog(@"select = %d",indexPath.row);
}
何も考えずに実装するなら、bgView は反時計回りに90°(-90°)回転させたほうが楽です。

そうすると UITableView の一番上のセルが左端になって、一番下のセルが右端なります。
左から右へスクロールするという iPhone の写真アプリと同じになるので都合がよいのです。

ではなぜそうしないかというと、反時計回りに90°回転させると UITableView のスクロールバーが画面の上端にきてしまうためです。
見栄え的にもウザいですし、何より UIScrollView を配置した時と、スクロールバーの位置が違うのでデザインの統一感がなくなってしまいます。

それ故、わざわざ時計回りに90°回転させています。
その結果、UITableView の一番下のセルが左端になるので、表示するセルの順番もそれに合わせてやらなければなりません。

その辺の処理は、tableView:cellForRowAtIndexPath: メソッドを見て頂けると分かると思います。
indexPath.row で得られる値を元に、表示する画像のファイル名を決めていますが
Sample1.png→Sample2.png→Sample3.png

とするところを

Sample3.png→Sample2.png→Sample1.png

となるようにしています。

動作デモ



関連記事

0 件のコメント:

コメントを投稿

Related Posts Plugin for WordPress, Blogger...