カテゴリー : 2011年 9月11日

UIViewControllerによるUIViewの回転 その1


iPhoneが登場したとき話題をさらったのは、デバイスを回転させるとそれに追随して中の画面も回転すること。いまとなっては当たり前のような機能だけど、登場した当時は魔法のように見えたものだった。

あまりに衝撃的で、かつ直感的で自然に見えたため、まるで回転に対応するアプリを作るのはとても簡単だと思われることもある。でもねー、かなり面倒くさいんだよね、これ。単純な話として、縦画面と横画面を作らなくてはいけないので、手間が倍になる。それはしょうがないからがんばるとして、どうやって回転を検知して、どうやって回転に対応する描画を行うか、というのが思ったよりも複雑だ。

ということで、回転にまつわるアレコレをまとめてみた。先に結論を言うと、回転に対応するただ一つの正解は存在しないみたいなので、アプリの仕様に応じていろいろと使い分けが必要になると思う。道具立てを紹介することは出来る。でも、どれを使うかはあなた次第。

では、回転をサポートする、もっとも簡単な方法から紹介していこう。それはもちろん、UIViewControllerを使うこと。UIViewControllerには、shouldAutorotateToInterfaceOrientation:というメソッドがある。これを上書きしてYESを返すようにすれば、それで回転することになる。

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation
{
    return YES;
}

実装の仕方によって、縦画面のサポートや横画面だけのサポート、またはあるタイミングでは回転を許して、あるタイミングでは許さない、ということも実現できる。

回転が発生すると、willRotateToInterfaceOrientation:duration:、willAnimateRotationToInterfaceOrientation:duration:、didRotateFromInterfaceOrientation:といったメソッドが順次呼び出される。回転時に特別なアニメーションを行いたいときは、これらの中で対応する。

というのが、簡単な使い方。

さて。もう少し突っ込んでみよう。そもそも、「回転」とは何だ?「回転が発生する」とは、いったい何が起きたことを意味するのだ?

答えは、UIViewの座標系が変化した、と言うことが出来る。UIViewには、大きさを表すbounds、アフィン変換を表すtransformといったプロパティがある。これらが変化することで、回転が実現されるのだ。UIViewControllerの場合、管理しているviewプロパティに対して、これらの属性をアニメーションとともに変化させることになる。

実験してみよう。UIViewContorllerにある回転イベントを通知するメソッドで、ビューのframe、bounds、transformといった値を表示させてみる。こんな感じのソースコード。

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation
        duration:(NSTimeInterval)duration
{
NSLog(@"### willRotateToInterfaceOrientation:duration ###");
NSLog(@"frame %@", NSStringFromCGRect(self.view.frame));
NSLog(@"bounds %@", NSStringFromCGRect(self.view.bounds));
NSLog(@"transform %@", NSStringFromCGAffineTransform(self.view.transform));
}

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation
        duration:(NSTimeInterval)duration
{
NSLog(@"### willAnimateRotationToInterfaceOrientation:duration: ###");
NSLog(@"frame %@", NSStringFromCGRect(self.view.frame));
NSLog(@"bounds %@", NSStringFromCGRect(self.view.bounds));
NSLog(@"transform %@", NSStringFromCGAffineTransform(self.view.transform));
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orientation
{
NSLog(@"### didRotateFromInterfaceOrientation: ###");
NSLog(@"frame %@", NSStringFromCGRect(self.view.frame));
NSLog(@"bounds %@", NSStringFromCGRect(self.view.bounds));
NSLog(@"transform %@", NSStringFromCGAffineTransform(self.view.transform));
}

結果はこんな感じ。

(縦から右横に回転)
### willRotateToInterfaceOrientation:duration ###
frame {{0, 20}, {320, 460}}
ounds {{0, 0}, {320, 460}}
transform [1, 0, 0, 1, 0, 0]
### willAnimateRotationToInterfaceOrientation:duration: ###
frame {{20, 0}, {300, 480}}
bounds {{0, 0}, {480, 300}}
transform [0, -1, 1, 0, 0, 0]
### didRotateFromInterfaceOrientation: ###
frame {{20, 0}, {300, 480}}
bounds {{0, 0}, {480, 300}}
transform [0, -1, 1, 0, 0, 0]

(右横から逆さまの縦に回転)
### willRotateToInterfaceOrientation:duration ###
frame {{20, 0}, {300, 480}}
bounds {{0, 0}, {480, 300}}
transform [0, -1, 1, 0, 0, 0]
### willAnimateRotationToInterfaceOrientation:duration: ###
frame {{0, 0}, {320, 460}}
bounds {{0, 0}, {320, 460}}
transform [-1, 0, -0, -1, 0, 0]
### didRotateFromInterfaceOrientation: ###
frame {{0, 0}, {320, 460}}
bounds {{0, 0}, {320, 460}}
transform [-1, 0, -0, -1, 0, 0]

まずは、transformに注目。最初は[1 0][0 1]。つまり、単位行列(identity)だ。次に[0 -1][1 0]。そして、[-1 0][0 -1]と、変化してく。つまり、90度ずつ回転していっているわけだね。この辺の数学的な話は省略。

続いてboundsを見てみる。最初は{320, 460}。次に{480, 300}。そして{320, 460}。これも、回転した画面の大きさにあわせて変化していっていることが分かる。

つまり、ビューの回転とは、boundsとtransformが変化した、ってことだ。

frameと、親ビューとの関係については、次回。