簡易スライドビューア [6] 拡大中の回転に対応する

2010年9月19日日曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

拡大中に回転させるといろいろ問題があることがわかった。

拡大画像を回転させた時の問題


例えばこんな画像を拡大させたとする。
拡大中はこんな感じ。
この状態で回転させるとこうなる。
位置が左上にリセットされている。またこの状態で拡大画像をスクロールすることができない。これは回転に合わせて UIScrollView.contentSize を変更している為。

内部的にはこんなふうになっているのだろう。下図は画像が拡大された状態で白い点線内が表示されている(十字は中心がわかるように描いてある)。

これを回転させた時、表示領域が左上にリセットされている。
こうではなくて、画面の回転に合わせて画像の中心で回転させてやる必要がある。
するとこうなる。
UIとしてはこっちの方が感覚的で正しい。


ロジック


拡大中の画像を回転後もきちんと表示するには中心座標を押さえておくのがポイント。回転後に中心座標から contentOffset を決めることができて、それによって意図した位置で拡大画像を表示することができる。
contentOffset.x = center.x - w/2
contentOffset.y = center.y - h/2
なので回転前の中心座標をとっておいて、それを回転後に使えばいい。

ただ今回の場合、座標系自体が回転しているので話が複雑になる。
例えば左回転した場合、ビューの原点はこんな感じになる。
つまり回転前の中心座標を回転後の座標に変換する必要がある。

この回転は変換行列を使って求められる(と思う)が、今回のケースではもっと簡単(原始的な?)な方法がある。これは回転前の中心位置を座標としてではなく表示ビュー内の相対位置(比)として取っておき、回転後はこの比を使って新しい中心位置を割り出す。

以下に手順を示す。

1. 回転前

UIScrollView.contentOffsetより回転前のオフセット座標がわかる(oldContentOffset)。ここから回転前の中心座標が求められる。
oldCenter.x = oldContentOffset.x + oldContentSize.width/2.0
oldCenter.y = oldContentOffset.y + oldContentSize.height/2.0
さらに全体の大きさから相対位置(比)が求められる。
ratioX = oldCenter.x / oldSize.width
ratioY = oldCenter.y / oldSize.height

2. 回転後
先に求めた比を元に最終的に設定すべきUIScrollView.contentOffsetを割り出す。
まず回転後の中心位置を求める。
newCenter.x = ratioX * newSize.width
newCenter.y = ratioY * newSize.height
中心位置が分かればオフセット座標は簡単に求められる。
newContentOffset.x = newCenter.x - newContentSize.width/2.0
newContentOffset.y = newCenter.y - newContentSize.height/2.0
これを UIScrollView.contentOffetへ入れてやれば回転前の中心が中心にくるような表示となる。

この方法の利点は回転方向と無関係に計算ができること。一見ややこしいが分岐が無いのでコードはすっきりするはず。


実装


上記のロジックをプログラムに落としこんでみる。こんな感じ。
- (void)layoutScrollViews
{
 CGSize newSize = self.view.bounds.size;
 CGSize oldSize = previousScrollSize_;
 previousScrollSize_ = newSize;

 // save previous contentSize
 //--
 ImageScrollView* currentScrollView =
  [self.imageScrollViews objectAtIndex:kIndexOfCurrentScrollView];
 CGSize oldContentSize = currentScrollView.contentSize;
 CGPoint oldContentOffset = currentScrollView.contentOffset;

 CGFloat zoomScale = currentScrollView.zoomScale;

 // [A] calculate ratio (center / size)
 CGPoint oldCenter;
 oldCenter.x = oldContentOffset.x + oldSize.width/2.0;
 oldCenter.y = oldContentOffset.y + oldSize.height/2.0;

 CGFloat ratioW = oldCenter.x / oldContentSize.width;
 CGFloat ratioH = oldCenter.y / oldContentSize.height;

 
 // set new origin and size to imageScrollViews
 //--
 CGFloat x = (self.contentOffsetIndex-1) * newSize.width;
 for (ImageScrollView* scrollView in self.imageScrollViews) {
  scrollView.frame = CGRectMake(x, 0, newSize.width, newSize.height);
  CGSize contentSize;
  if (scrollView == currentScrollView) {
   contentSize.width  = newSize.width  * scrollView.zoomScale;
   contentSize.height = newSize.height * scrollView.zoomScale;
  } else {
   contentSize = newSize;
  }
  scrollView.contentSize = contentSize;
  x += newSize.width;
 }
 
  
 // [B] adjust current scroll view for zooming
 //--
 if (zoomScale > 1.0) {
  CGSize newContentSize = currentScrollView.contentSize;

  CGPoint newCenter;
  newCenter.x = ratioW * newContentSize.width;
  newCenter.y = ratioH * newContentSize.height;

  CGPoint newOffset;
  newContentOffset.x = newCenter.x - newSize.width /2.0;
  newContentOffset.y = newCenter.y - newSize.height/2.0;
  currentScrollView.contentOffset = newContentOffset;
 }
 
 // adjust content size and offset of base scrollView
  :
}
[A] で ratioX, ratioY を求め、[B]で newOffsetを求めている。なお回転中の処理では oldSize のみ求めることができないのでこれはメンバ変数を用意してとっておく。
@interface EasyGalleryViewController : UIViewController {

  :
 CGSize previousScrollSize_;
}
回転処理中に UIScrollView.frame.size で取得できるような記述をどこかでみたのだが、試してみるとシミュレーターでは横⇒縦のケースでは取得できなかった(回転後のサイズとなっていた)。


サンプル


さて実行してみよう。
拡大して
回転する。

出た。そのまま回転を続けても中心は変わらない(※端の方は表示領域の関係で補正される)。


ソースコード


GitHubからどうぞ。
EasyGallery at 2010-09-19 from xcatsan's iOS-Sample-Code - GitHub


- - - - -
たかが拡大時の位置補正、なんて高を括っていたら見事にハマってしまった。方法はともかく動いてよかった。


(追記) なおこのブログを書き終えた後で気がついたのだが実はこんなに大げさな計算はいらない。拡大したままの回転でのポイントは zoomScaleの扱い。
CGFloat x = (self.contentOffsetIndex-1) * newSize.width;
 for (ImageScrollView* scrollView in self.imageScrollViews) {
  scrollView.frame = CGRectMake(x, 0, newSize.width, newSize.height);
  CGSize contentSize;
  if (scrollView == currentScrollView) {
   contentSize.width  = newSize.width  * scrollView.zoomScale;
   contentSize.height = newSize.height * scrollView.zoomScale;
  } else {
   contentSize = newSize;
  }
  scrollView.contentSize = contentSize;
  x += newSize.width;
 }
回転処理中に contentSize を計算しているが、ここで *scrollView.zoomScale をいれればこれだけで済んでしまう(以前は入っていなかった)。これに気がつかないが為に延々と時間を費やしてしまった...。ただこの方法だと中心位置は微妙にずれる。計算した場合は中心位置がずれないのでそれを慰めに?良しとしよう。

Responses

Leave a Response

人気の投稿(過去 30日間)