● Carbon視点でiPhone探求(2010/05/27)

  この記事は、MOSAの会員にのみ読むことができるデベロッパー向けの
  ウェブサイトMOSADeN Onlineのに掲載された記事です。ほぼ一ヶ月
  遅れでここに掲載されて行きます。

  〜 アバウト画像も回転させよう! 〜


今回は、前回解説することができなかったアバウトの横表示(デバイス回転対応)にチャレンジします。つでに対称処理を実行した時の日付をModelオブジェクトへと保存し、画像一覧に表示できるようにしてみます。

まず最初に、Modelクラスに日付データ保存用のインスタンス変数を用意します。編集を実行した時の日付は、このmd_date(NSDate)に保存されます。新しく定義を追加するのはModel.hです。

@interface Model : NSObject  <NSCoding>
{
  NSInteger   md_id;
  NSString   *md_name;
  NSString   *md_type;
  NSDate    *md_date;   // 最終編集日付
  UIImage    *md_image;  // 対称処理する画像
  CGRect    md_rt;
  NSUInteger  md_flag;
  NSInteger   md_kind;   // 対称処理の方向
  CGFloat    md_para0;  // オフセット値(上下)
  CGFloat    md_para1;  // オフセット値(左右)
}

@property(nonatomic,retain)NSDate    *md_date;

前回と同様に、Modelクラスのインスタンス変数の構成を変更したら、ファイルの読み込み時に使うinitWithCoder:メソッドと、ファイルへ保存する時に使うencodeWithCoder:メソッドも拡張します。NSDateクラスはNSStringクラスと同様にNSCodingプロトコルに準拠していますので追加は簡単です。今回の変更でも、以前に保存したファイルとの互換性(コンパチビリティー)がなくなりますので、先んじてシミュレータ上の旧「しんぶんし」アプリを削除してから、今回分の開発を続行するようにしてください。

- (void)encodeWithCoder:(NSCoder *)coder
{
  [coder encodeObject:md_name];
  [coder encodeObject:md_type];
  [coder encodeObject:md_date]; // 新規追加した日付の処理
  [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(CGFloat) at:&md_para0];
  [coder encodeValueOfObjCType:@encode(CGFloat) at:&md_para1];
}

- (id)initWithCoder:(NSCoder *)coder
{  
  if( self=[super init] )
  {
    self.md_name=[coder decodeObject];
    self.md_type=[coder decodeObject];
    self.md_date=[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(CGFloat) at:&md_para0];
    [coder decodeValueOfObjCType:@encode(CGFloat) at:&md_para1];
  }
  return self;
}

続いて、ImageViewController.mのviewWillDisappear:メソッドに日付を保存するための処理を追加します。これにより、対称操作が終り画像一覧に戻る時点でModelオブジェクトに現在の日付が記録されます。

- (void)viewWillDisappear:(BOOL)animated
{
  SymmetryAppDelegate  *app;
  
  [super viewDidAppear:animated];
  
  im_model.md_date=[NSDate date];         // 現在の日付を設定
  im_model.md_kind=im_type.selectedSegmentIndex; // 対称方向を設定  
  im_model.md_para0=im_view0.sy_para;  // オフセット値(上下)を設定  
  im_model.md_para1=im_view2.sy_para;  // オフセット値(左右)を設定  
  
  app=[[UIApplication sharedApplication] delegate];
  [app.ap_document save];  // ドキュメントのファイル保存
}

次は、画像一覧(RootViewController)の各行に日付を表示する処理の追加です。今まではUITableViewCellオブジェクトを作成する時に、initWithFrame:reuseIdentifier:メソッドを利用していましたが、今回はinitWithStyle:reuseIdentifier:メソッドを使います(iPhone OS 3.0から利用可能)。利用するUITableViewCellのスタイル(種類)は、各行にサブタイトルが表示できるUITableViewCellStyleSubtitleとします。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  Model      *model;
  UITableViewCell  *cell;
  NSDateFormatter *form;
  
  cell=[tableView dequeueReusableCellWithIdentifier:@"ImageCell"];
  if( ! cell )
    cell=[[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"ImageCell"] autorelease];
  if( model=[rt_array objectAtIndex:indexPath.row] )
  {
    if( model.md_date ) // 日付が保存されていれば編集済み
    {
      if( form=[[NSDateFormatter alloc]init] ) // フォーマッタ作成
      {
        [form setDateStyle:kCFDateFormatterMediumStyle];
        cell.detailTextLabel.text=[form stringFromDate:model.md_date];
        [form release]; // 日付表示を実行した後にフォーマッタを解放
      }
    }
    else
      cell.detailTextLabel.text=nil; // 日付表示はしない
    cell.textLabel.text=model.md_name;
    cell.imageView.image=model.md_image;
    cell.accessoryType=UITableViewCellAccessoryDetailDisclosureButton;  
  }
  return cell;
}

model.md_dateに日付が保存されていなければ(未編集なら)、サブタイトルには何も表示されません。日付が保存されていれば、表示する日付のフォーマットを決めるために NSDateFormatterオブジェクトを作成して好みのスタイルを選びます。その後に、テーブルビューセルのdetailTextLabelプロパティ(UILabel)のtext(NSString)にフォーマッタで成型した文字列を代入します。すると画像名称の下に、少し子ぶりな文字サイズ(グレイ)で日付が表示されます。

  

例えば、NSDateFormatterクラスのsetDateStyle:メソッドに対してkCFDateFormatterMediumStyleの代わりにkCFDateFormatterFullStyleを渡せば、以下のような表示となります。日付スタイルを色々と変更して試してみてください。

  

続いてアバウト表示をデバイスの回転に対応させます。現状ではデバイスを横にしてもアバウト表示は回転しません。その理由は、今までのアバウト表示ではビューコントローラ(AboutViewController)の機能が働いておらず、AboutViewController.mでオーバライドされているshouldAutorotateToInterfaceOrientation:メソッドが役立たないためです。ビューコントローラのビューの自動回転を生かすには、アニメーション処理のためにアバウト表示を貼り付けているビューを、window(UIWindow)からナビゲーションコントローラのビュー(navigationController.view)へと変更します。具体的には setAnimationTransition: forView:メソッドに渡しているビューと、addSubview:メソッドの対象となるビューをnavigationController.viewへ切り替えます。これによりアニメーション後の回転はImageViewControllerで一括処理されます。

- (void)openAboutViewController // アバウトビュー表示
{
  AboutViewController *aboutvctr;
  UIDevice       *dev;
  CGRect        srt;
  
  if( aboutvctr=[[AboutViewController alloc]initWithNibName:@"AboutViewController" bundle:nil] )
  {    
    dev=[UIDevice currentDevice]; // デバイスの向きが横方向なら
    if( dev.orientation==UIDeviceOrientationLandscapeRight || dev.orientation==UIDeviceOrientationLandscapeLeft )
    {
      srt=[UIScreen mainScreen].bounds; // スクリーンの矩形枠を得る
      aboutvctr.view.frame=CGRectMake( 0,0,srt.size.height,srt.size.width ); // 幅と高さを入れ替えてセット
    }
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:1.0];
    [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:navigationController.view cache:YES];
    [navigationController.view addSubview:aboutvctr.view];
    [UIView commitAnimations];
  }
}

デバイスが横向きの場合には、アバウト表示のビューフレーム(矩形枠)に対してスクリーンの幅と高さを逆にしてセットします。つまり、CGRectMake(0,0,480,320)を実行したのと同等です。また、上記の仕組みを正しく動かすためには、Interface BuilderでAboutViewController.xibに登録されているアバウト用のすべてのビュー(土台ビュー、UIImageView、UILabel)に対して、インスペクタのサイズ設定において、Autosizing用の6つの赤い矢印をすべてONにしておく必要があります。

  

この状態で実行してみると、デバイスの回転が起こる度にアバウトの裏側に隠れているナビゲーションバーがチラチラと表示されて美しくありません。そこで、アバウト表示を行う前に、一旦ナビゲーションバーを消して(Hide)おくことにします。この処理はImageViewController.mのabout:アクションメソッドに記述します。

- (IBAction)about:(id)sender // (i)ボタンのタップ
{
  SymmetryAppDelegate  *app;
  
  app=[[UIApplication sharedApplication] delegate]; // アプリケーションデリゲート  
  [app.navigationController setNavigationBarHidden:YES animated:NO] // ナビゲーションバーを消す
  [app openAboutViewController];  // アバウトビューをオープン
}

逆にアバウト表示から編集画面に戻る時には、ナビゲーションバーを再表示させます。こちらの処理はAboutViewController.mのcloseメソッドに追加します。また、復帰用のアニメーション処理でも、操作対象となるビューを、window(UIWindow)からナビゲーションコントローラが管理しているビュー(app.navigationController.view)へと切り替えておく必要があります。御注意ください。

- (void)close // アバウト画像を閉じる
{
  SymmetryAppDelegate  *app;
  
  app=[[UIApplication sharedApplication] delegate]; // アプリケーションデリゲート  
  [app.navigationController setNavigationBarHidden:NO animated:NO]; // ナビゲーションバーを表示
    
  [UIView beginAnimations:nil context:NULL];
  [UIView setAnimationDuration:1.0];
  [UIView setAnimationTransition:UIViewAnimationTransitionCurlDown forView:app.navigationController.view cache:YES];
  [self.view removeFromSuperview];
  [UIView commitAnimations];
  [self dealloc];
}

  

次回は「保存」ボタンをタップすることで、対称処理した画像を「写真アルバム」(写真アプリのフォトライブラリー)に保存してみます。余裕があれば、対称画像の「コピー」にもチャレンジする予定です。お楽しみに!

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