● Carbon視点でiPhone探求(2009/09/07)

  この記事は、MOSAの会員にのみ配布されているデベロッパー向けの
  デジタルマガジンMOSADeNのに掲載された記事です。ほぼ一ヶ月遅れで
  ここに掲載されて行きます

 〜 テーブルビューにサムネイルを表示する 〜


前回はテーブルビューにファイル名しか表示しませんでした。今回は、ファイル名に加えて行の先頭に画像を表示したいと思います。こうした場合、使用メモリの増加を防ぐために、オリジナル画像から小画像(サムネイル)を作成します。

まず最初に、画像を追加するための「+ボタン」をナビゲーションバーの右側に配置します。 Interface BuilderでMainWindow.xibをオープンし、ライブラリから「Bar Button Item」(UIBarButtonItem)を選んでナビゲーションバーの右上にドロップします。続いて、それを選択し、インスペクターのアトリビュート設定の「Identfier」で「Add」を選びます。この操作により、ボタンのタイトルが「+」に変わるはずです。続いて、このボタンのタップで実行される以下のアクションメソッドを、RootViewController.mに実装します。

- (IBAction)addImage:(id)sender;

このメソッドは、以前の「UIImagePickerControllerで画像取り込み」の回で解説したgetImage:メソッドをそのまま使用します(名称変更だけ行う)。もう一度Interface Builderに戻り、MainWindow.xibのNavigation Controllerウィンドウに表示されているLoad From "RootViewController"(青い文字表示)のエリアを選び、インスペクターのコネクション設定に表示されている「addImage:」アクションをナビゲーションバーの「+」ボタンに接続します。これにより「+」ボタンがタップされた時に、addImage:メソッドが呼び出されて実行されることになります。

同様に、以前作成したUIActionSheetDelegateとUIImagePickerControllerDelegateメソッドをRootViewController.mに追加します。デリゲートメソッドは以下の3つです。

(1)アクションシートでどれかボタンがタップされると呼び出される
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:
                        (NSInteger)buttonIndex;

(2)UIImagePickerControllerで画像が選択されると呼び出される
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo;

(3)UIImagePickerControllerでの操作がキャンセルされると呼び出される
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker;

この中で、新たに処理内容を追加しなければならないのは2番目のメソッドのみです。残りの2つについては、以前の内容から変更はありません。処理内容を追加した新しい2番目のデリゲートメソッドは以下のようになります。

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo
{
  Model         *model;
  SymmetryAppDelegate *app;

  app=(SymmetryAppDelegate *)[[UIApplication sharedApplication] delegate];
  if( model=[app.ap_document addModelWithName:@"Photo" type:@"Image"] )
  {
    model.md_image=resizeUIImage( 1,image,40,40 ); // サムネイル画像登録
    [model saveImage:image]; // 画像ファイルを保存
    [app.ap_document save]; // ドキュメントを保存
  }
  [self dismissModalViewControllerAnimated:YES]; // 画像ピッカーを閉じる
}

まずは、前回のaddModelWithName: type:メソッドでモデルオブジェクトをひとつ作ります。とりあえず名称は「Photo」とします。その次のresizeUIImage()は「画像の矩形サイズを調整する」の回で解説したサムネイルを作成するための画像縮小ルーチンです。イメージピッカーから受け取った画像(UIImage)を渡し、40x40ピクセル内に縮小したサムネイル(同じくUIImage)を得ます。これをモデルオブジェクトのmd_imageインスタンス変数に保存します。オリジナル画像は、モデルクラスのsaveImage:メソッドでJPEGファイルとして保存し、最後に修正(モデルが追加)されたドキュメントオブジェクト(アーカイブ)そのものをsaveメソッドでファイルへと保存します。

以下が、得られた画像(UIImage)をJPEGファイルとして保存するためのsaveImage:メソッドです。保存されるファイル名は、モデル名の後ろに識別子(md-id)を追加した書式(Photo-1.jpgなど)となります。 UIImageは、UIImageJPEGRepresentation()ルーチンを使い、先んじてNSDataオブジェクトに変換しておきます(0.5はJPEGの圧縮レベル)。ファイルのパス名を得るため用いているgetDocumentPath()は、「Documentクラスを用意する」の回で解説した、Documentsディレクトリを含めたフルパス名を得るためのユーティリティルーチンです。

- (BOOL)saveImage:(UIImage *)image
{
  NSString  *str,*name,*path;
  BOOL   ret=NO;
  NSData   *data;

  if( data=UIImageJPEGRepresentation( image,0.5 ) )  // UIImage->NSData
  {
    str=[NSString stringWithFormat:@"%@-%d",md_name, md_id];
    name=[str stringByAppendingString:@".jpg"]; // 拡張子を追加
    path=getDocumentPath( name ); // ファイルのフルパス名を得る
    ret=[[NSFileManager defaultManager] createFileAtPath:path contents:data attributes:nil];
  }                   // jpegファイルの保存
  return ret;
}

次は、同じく「Documentクラスを用意する」の回で解説したアーカイブを保存するためのsaveメソッドです。こちらも再度掲載しておきます。逐次保存するのが嫌な場合は、アプリ終了時に実行しても良いのですが、そうすると途中でアプリがクラッシュした場合に、それまでの編集結果が失われることになります。シミュレータでこの状況を試すのには、作業途中でシミュレータアプリをQuitしてしまいます。

- (BOOL)save
{
  BOOL  ret=NO;

  if( do_array )
    ret=[NSKeyedArchiver archiveRootObject:do_array toFile:[self path]];  
  return ret;
}

さて、前回追加した識別子(md_id)と今回作ったサムネイル(md_image)を「iPhoneでもNSCodingプロトコルのお世話に」の回で解説した、アーカイブ処理に追加します。予定では、サムネイルはオリジナル画像ファイルから逐次作成する予定でしたが、こちらのデータもアーカイブに含めてしまうことにしました。

(void)encodeWithCoder:(NSCoder *)coder // モデルのエンコード処理
{
  [coder encodeObject:md_name];
  [coder encodeObject:md_type];
  [coder encodeDataObject:UIImageJPEGRepresentation( md_image,0.5 )];
  [coder encodeDataObject:[NSData dataWithBytes:&md_rt length:sizeof(CGRect)]];
  [coder encodeValueOfObjCType:@encode(NSInteger) at:&md_id];
  [coder encodeValueOfObjCType:@encode(NSUInteger) at:&md_flag];
  [coder encodeValueOfObjCType:@encode(NSInteger) at:&md_kind];
  [coder encodeValueOfObjCType:@encode(NSInteger) at:&md_para];
}


- (id)initWithCoder:(NSCoder *)coder // モデルのデコード処理
{
  if( self=[super init] )
  {
    self.md_name=[coder decodeObject];
    self.md_type=[coder decodeObject];
    self.md_image=[UIImage imageWithData:[coder decodeDataObject]];
    [[coder decodeDataObject] getBytes:&md_rt length:sizeof(CGRect)];
    [coder decodeValueOfObjCType:@encode(NSInteger) at:&md_id];
    [coder decodeValueOfObjCType:@encode(NSUInteger) at:&md_flag];
    [coder decodeValueOfObjCType:@encode(NSInteger) at:&md_kind];
    [coder decodeValueOfObjCType:@encode(NSInteger) at:&md_para];
  }
  return self;
}

ちなみに、以前の解説でCGRect構造体のエンコードとデコードを以下のように記述していましたが、これは大間違いです(スミマセン)。今回の処理では、ちゃんとCGRect構造体をNSDataに変換してから処理を行うように修正してあります。

[coder encodeValueOfObjCType:@ encode(CGRect) at:& md_rt];
[coder decodeValueOfObjCType:@ encode(CGRect) at:& md_rt];

続いて、RootViewControllerのtableView: cellForRowAtIndexPath:デリゲートに、行の先頭にサムネイルを表示するための一行を追加します。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  Model      *model;
  UITableViewCell  *cell;
          // テーブル表示用のセルを得る(無ければ作成)
  cell=[tableView dequeueReusableCellWithIdentifier:@"ImageCell"];
   if( ! cell )
    cell=[[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"ImageCell"] autorelease];
  if( model=[rt_array objectAtIndex:indexPath.row] ) // 指定行のモデルを得る
  {
    cell.textLabel.text=model.md_name;   // 指定行にファイル名を表示
    cell.imageView.image=model.md_image; // サムネイルを表示(今回追加)
  }
  return cell;
}

最後に、RootViewControllerのビューが表示される時にreloadDataメソッドを呼び出して、テーブルビューの再表示を行います。これを実行しないと、画像を登録してもテーブルビュー自体が更新されませんので、ご注意ください。

- (void)viewWillAppear:(BOOL)animated // ビューの表示開始
{
  SymmetryAppDelegate  *app;

  [super viewWillAppear:animated];
  app=(SymmetryAppDelegate *)[[UIApplication sharedApplication] delegate];
  self.rt_array=app.ap_document.do_array;
  [self.tableView reloadData];        // テーブルを再描画
}

今回は、画像をファイルへ保存する処理を解説しました。次回は、テーブルビューに登録されている行をタップすることで、ファイルからJPEG画像を呼び出し、別のビューコントローラに切り替えて表示する処理を解説したいと思います。

copyright 2009 Ottimo, Inc. All rights reserved
無断転載・引用禁止