● Carbon視点でiPhone探求(2010/04/24)

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

  〜 ユーザインターフェースのチューニング 〜


前回までだと対称画像が画面いっぱいに表示されず(横長画像の左右の場合)あまり見栄えが良くありません。今回はこの画像表示の仕組みと、タッチユーザインターフェースのチューニングを行いたいと思います。

まず最初の改良です。前回までは、「上下、左右」セグメンテッドコントロールと「逆方向」ボタンで対称方向を決定していたのですが、操作が煩雑なので、両機能をひとつのセグメンテッドコントロールにまとめます。Interface BuilderでImageViewController.xibをオープンし「逆方向」ボタンを削除してから、セグメンテッドコントロールを「上、下、左、右」の4種類選択に変更します。セグメンテッドコントロールのどれかを選択すると、その位置に(上なら上部に)オリジナル画像が表示されるルールとします。

 

この変更に伴い、ImageViewController.mのdirection:メソッドを以下のように修正します。加えて、「逆方向」ボタンをタップした時に呼ばれるアクションメソッドのreverse:は必要なくなりますので廃棄します。

- (IBAction)direction:(UISegmentedControl *)sender
{
  NSUInteger  select;
  
  select=sender.selectedSegmentIndex; // セグメンテッドコントローラ選択値
  
  if( select==0 )    //  上
  {
    im_view0.sy_reverse=NO;
    im_view1.sy_reverse=NO;
  }
  else if( select==1 )  //  下
  {
    im_view0.sy_reverse=YES;
    im_view1.sy_reverse=YES;
  }
  else if( select==2 )  //  左  
  {
    im_view2.sy_reverse=YES;
    im_view3.sy_reverse=YES;
  }
  else          //  右
  {
    im_view2.sy_reverse=NO;
    im_view3.sy_reverse=NO;
  }
  
  if( select <=1 )     //  上下
  {
    im_view0.sy_offset=im_view1.sy_offset=0.0;
    [self.view bringSubviewToFront:im_view0];
    [self.view bringSubviewToFront:im_view1];
    [im_view0 setNeedsDisplay];
    [im_view1 setNeedsDisplay];
  }
  else          //  左右
  {
    im_view2.sy_offset=im_view3.sy_offset=0.0;
    [self.view bringSubviewToFront:im_view2];
    [self.view bringSubviewToFront:im_view3];
    [im_view2 setNeedsDisplay];
    [im_view3 setNeedsDisplay];
  }
}

続いて、対称の境目を変更する時のタップ処理を改良します。今までだと「上下」なら必ず上から下へのドラッグで、「左右」なら必ず左から右へのドラッグで対称の境目が変更されていました。これを最初のタップ位置を判断しすることで切り替えるようにします。つまり、上の画像をタップした場合には上から下へ、下の画像をタップした場合には下から上へのドラッグで対称の境目を変更させます。また「左右」の対称処理でも同じ方法を採用します。 これにより境目を変更する操作がより自然になりました。具体的にはImageViewController.mのtouchesMoved:メソッドを以下のように修正します。

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event // 移動中
{
  CGPoint    vpt1,vpt2;
  CGFloat    off;

  for( UITouch *touch in touches )
  {
    vpt1=[touch previousLocationInView:self.view];
    vpt2=[touch locationInView:self.view];
    break;
  }
  if( im_type.selectedSegmentIndex <=1 )  //  上下
  {
    if( CGRectContainsPoint( im_view0.frame,vpt1 ) )  // どのビュー内か?
      off=im_view0.sy_offset+vpt1.y-vpt2.y;
    else
      off=im_view1.sy_offset+vpt2.y-vpt1.y;
    if( off >=0.0 )
    {
      im_view0.sy_offset=im_view1.sy_offset=off;
      [im_view0 setNeedsDisplay];
      [im_view1 setNeedsDisplay];
    }
  }
  else                  //  左右
  {
    if( CGRectContainsPoint( im_view2.frame,vpt1 ) ) // どのビュー内か?
      off=im_view2.sy_offset+vpt1.x-vpt2.x;
    else
      off=im_view3.sy_offset+vpt2.x-vpt1.x;
    if( off >=0.0 )
    {
      im_view2.sy_offset=im_view3.sy_offset=off;
      [im_view2 setNeedsDisplay];
      [im_view3 setNeedsDisplay];
    }
  }
}

最後は、対称画像が画面最大に表示されない問題です。この現象には画像の縦横比が関係しています。横長の画像は「上下」対称では、ほぼ画面いっぱいに表示して操作できますが、「左右」対称では表示が小さくなってしまいます。縦長の画像ではその逆の状態となります。そこで、iPhone(デバイス)の回転方向に対応して、ダイナミックに表示状態を切り替えることにします。横長の画像を「上下」対称にしたければデバイスを縦にして編集し、「左右」対称にしたければデバイスを横にして編集すれば、境目の位置決定をしやすくなるわけです。

ビューコントローラ内のビューをデバイスの回転に合わせて正しく再配置するために、Interface BuilderでImageViewController.xibを編集します。まずは、4つのシンメトリービューを配置する土台のビュー(UIView)を追加し、それをIBOutletとして用意したim_baseへコネクトします。

IBOutlet UIView *im_base;  //  ベース(土台)ビュー

 

このビューの上に今までの4つのシンメトリービュー(im_view0, im_view1, im_view2, im_view3)を配置し、インスペクターで「Autorisize」(6本の赤い矢印)を図のように変更します。図は例題としてボタンコントロールを配置して示しています。これにより、デバイスが回転した時に4つのシンメトリービューが自動で正しく再配置されます。また、土台となるビュー(im_base)についても、インスペクターで「Autorisize」のすべての矢印(6本)がアクティブになっていることを確認してください。

 

 

 

 

続いてデバイス回転をビューコントローラに伝えるために、ImageViewController.mでオーバライドしているshouldAutorotateToInterfaceOrientation:を以下のように変更します。加えて、RootViewController.mやDetailViewController.mについても同様な変更を行います。

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
  return interfaceOrientation!=UIDeviceOrientationPortraitUpsideDown;
}

これにより、デバイスの方向が変更される度に以下のメソッドが呼ばれますので、その中で対称処理の初期化(セグメンテッドコントロールをタップした時と同じ)を行います。

- (void)willAnimateRotationToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation duration:(NSTimeInterval)duration
{
  [self direction:im_type];   //  デバイスの回転が起こった(初期化)
}

今までシンメトリビューはビューコントローラのビュー(self.view)に配置されていましたが、今回からは土台ビュー(im_base)に配置されますので、アクションメソッドのdirection:で呼び出しているbringSubviewToFront:メソッドの記述も変更しておきます。
  
  if( select <=1 )     //  上下
  {
    im_view0.sy_offset=im_view1.sy_offset=0.0;
    [im_base bringSubviewToFront:im_view0];  // 変更
    [im_base bringSubviewToFront:im_view1];  // 変更
    [im_view0 setNeedsDisplay];
    [im_view1 setNeedsDisplay];
  }
  else          //  左右
  {
    im_view2.sy_offset=im_view3.sy_offset=0.0;
    [im_base bringSubviewToFront:im_view2];  // 変更
    [im_base bringSubviewToFront:im_view3];  // 変更
    [im_view2 setNeedsDisplay];
    [im_view3 setNeedsDisplay];
  }

また、今回はInterface Builderですべてのシンメトリビューを正しく初期配置しましたので、 以下のviewDidLoadメソッドで実行していたaddSubview:メソッドによる再配置処理も必要ありません。すべて削除してしまいます。

- (void)viewDidLoad
{
[super viewDidLoad];
  
  im_view0.sy_type=0;            //  下部の画像表示用(上下)
  im_view0.sy_image=[im_model loadImage];  //  画像を読み込む
  
  im_view1.sy_type=1;            //  上部の画像表示用(上下)
  im_view1.sy_image=im_view0.sy_image;

  im_view2.sy_type=2;            //  左部の画像表示用(左右)
  im_view2.sy_image=im_view0.sy_image;

  im_view3.sy_type=3;            //  右部の画像表示用(左右)
  im_view3.sy_image=im_view0.sy_image;
  
  im_type.selectedSegmentIndex=0;      //  ディフォルトは上下
  
  self.navigationItem.rightBarButtonItem=im_save; // 保存ボタンを追加  
  im_type.tintColor=[UIColor grayColor];      // タイプセグメントカラー
}

これらの処理を追加することで、iPhoneを横向きにした時でも対称処理が継続できるようになりました。横長の画像の「左右」対称を実行したい時は、iPhoneを横向きにすることで操作しやすくなります。

 

次回は、対称処理の結果(対称方向とオフセット値)を「保存」ボタンでモデルオブジェクトへ保存できるようにします。また、アバウトの横表示にもチャレンジする予定です。

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