回転方向は最短
iOSでtransform
を使ってViewを回転させる場合、回転方向を指定することができない(指定するパラメタが無い)ことはご存知だろう。
回転の方向は、OSが現在の角度から指定された角度へ最短距離(角度)で移動できる方向に決まっているようだ。
以下、時計の針のような図形(View)を用いて説明する。
図形の最初の位置を0時とする。ここから3時の方向へ回転させると、右回転(時計回り)で回転する。
view.transform = CGAffineTransform(rotationAngle: .pi / 180 * 90) | ||
0時 |
→ | 3時 |
次に、3時の位置から、11時へ回転させると、左回転(反時計回り)で回転する。つまり、移動距離が短い(回転させる角度が小さい)方向に回転する。
view.transform = CGAffineTransform(rotationAngle: .pi / 180 * 330) | ||
3時 |
→ | 11時 |
実験した結果、回転させる角度が180度未満の方向に回転し、3時から9時のように丁度180度の場合は右回りとなった。
なお、CGAffineTransform
メソッドのrotationAngle
引数で指定する角度は、相対角度ではなく絶対角度である。
常に右回転させたい
では、常に右回転したい場合、どのようにすればよいのか?
『角度の小さい方へ回転する』特性を利用して、必ず右回りになるような(180度未満の)中継点を設定すればよい。具体的には、3時から11時へ右回転したい場合は、$(330-90)/2+90=$210度を中継してから330度に移動すれば右回転となる。90度→210度→330度
view.transform = CGAffineTransform(rotationAngle: .pi / 180 * 210) view.transform = CGAffineTransform(rotationAngle: .pi / 180 * 330) | ||
3時 |
→ | 11時 |
関数化
常に右回転させる処理を関数化すると以下のようになる。
角度の指定方法として度数とラジアンの両方に対応しておく。
func clockwiseRotate(_ view: UIView, degree: CGFloat) {
clockwiseRotate(view, radian: degree * .pi / 180)
}
func clockwiseRotate(_ view: UIView, radian: CGFloat) {
var now = atan2(view.transform.b, view.transform.a)
if now < 0 { now += .pi * 2 }
let transit = (radian < now) ? ((radian + .pi * 2) - now) / 2 + now : (radian - now) / 2 + now
view.transform = CGAffineTransform(rotationAngle: transit)
view.transform = CGAffineTransform(rotationAngle: radian)
}
clockwiseRotate(view, degree: 90.0) // 現在の角度(0時)から90度(3時)へ回転
clockwiseRotate(view, degree: 330.0) // 現在の角度(3時)から330度(11時)へ回転
この関数のポイントは、viewの現在の角度から目的の角度までの中継角度を求めるところで、
・変数now
にviewの現在の角度を求める。負数になる場合があるので$2\pi$を足して正数化。
・変数transit
に中継する角度を求める。
ちなみに
回転方向を確認するためには、アニメーションでビューの変化を可視化する必要がある。回転後のビューを見せるだけなら、回転方向など意味がないので。
clockwiseRotate(view, degree: 90)
UIView.animate(withDuration: 1.0, delay: 0.0, options: [], animations: {
clockwiseRotate(view, degree: 330)
})
右回転の例
次々にランダムな時刻を(常に右回りで)表示してみた。いかがであろうか。