【Swift】Rotation Gesture Recognizerの使い方。2つの指で部品を回転させる。(Swift 2.1、XCode 7.2)
Rotation Gesture Recognizerとは
本記事ではSwiftで使える部品のRotation Gesture Recognizer(以下、ローテーションリコグナイザー)について説明する。
ローテーションリコグナイザーとは、部品をタッチしている2つの指の座標を結ぶ直線が回転したことを検知する部品である。
前回記事のピンチリコグナイザーと使い方が似ているが、拡大縮小に比べると回転の使用頻度は多く無い。ということは、新しいアイディアがそこに埋まっているかも知れない。⇒「ピンチリコグナイザーとは」
ローテーションリコグナイザーを使ってみる
実際にローテーションリコグナイザーを使ってみよう。
デバイス画面にラベルを配置する(下図赤矢印)。黄枠のアトリビュートインスペクタボタンを押して設定画面を表示する。Textに「テスト」、Colorを白、Backgroundに青、User Interaction Enabledにチェックを入れる。
紫枠のアシスタントエディタボタンを押してViewController.swfitを開く。Ctrlキーを押しながらラベルをドラッグ&ドロップでソースコードまで運んで吹き出しの設定画面を表示させる(黄緑矢印)。
Connectionに「Outlet」、Nameに「testLabel」を入力しConnectボタンを押す。これでラベルをソースコードから操作できるようになった。
ローテーションリコグナイザーをラベルに配置する(下図赤矢印)。Ctrlキーを押しながら黄緑枠のローテーションリコグナイザーをソースコードまで運んで吹き出しの設定画面を表示させる(青矢印)。
Connectionに「Action」、Nameに「rotateLabel」、Typeに「UIRotationGestureRecognizer」を設定し、Connectボタンを押す。これで2つの指が回転したときのイベントをソースコードで受けれるようになった。
ViewController.swiftを以下のコードに変更する。ピンチリコグナイザーと同じように「回転時の呼び出しメソッド」が指を回すたびに呼び出される。
前回ドラッグ終了時の回転をクラス変数に保存し、ドラッグ中はこの回転を引き継いだアフィン変換を行っている。これにより、回転位置が元に戻ることなく回転を再開することができるということだ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// // ViewController.swift // import UIKit class ViewController: UIViewController { @IBOutlet weak var testLabel: UILabel! var startTransform:CGAffineTransform! //最初からあるメソッド override func viewDidLoad() { super.viewDidLoad() } //回転時の呼び出しメソッド @IBAction func rotateLabel(sender: UIRotationGestureRecognizer) { if(sender.state == UIGestureRecognizerState.Began) { //前回ドラッグ終了時の回転をクラス変数に保存する。 startTransform = testLabel.transform } //前回ドラッグ終了時の回転を引き継いでアフィン変換を行う。 testLabel.transform = CGAffineTransformRotate(startTransform, sender.rotation) } } |
以下は実際のプレイ動画。ちなみにシミュレーターで2本の指を使うにはOptionキーを押しながら操作する。
拡大縮小と回転を同時に行う
ピンチリコグナイザーとローテーションリコグナイザーを使ってみて思うことといえば「拡大縮小と回転を同時に行いたい」である。同時に行うためのデリゲートが用意されているので使ってみよう。以降の手順は上記手順の続きである。
ピンチリコグナイザーをラベルに配置する(下図赤矢印)。黄緑枠のアシスタントエディタボタンを押してViewController.swiftを開く。Ctrlキーを押しながら紫枠のピンチリコグナイザーをドラッグ&ドロップでソースコードまで運んで吹き出しの設定画面を表示させる。
Connectionに「Action」、Nameに「pinchLabel」、Typeに「UIPinchGestureRecognizer」を入力してConnectボタンを押す。これで、ピンチイン、ピンチアウトのイベントをソースコードで受けれるようになった。
もう一度、Ctrlキーを押しながら紫枠のピンチリコグナイザーをドラッグ&ドロップでソースコードまで運んで吹き出しの設定画面を表示させる。Connectionに「Outlet」、Nameに「pinchRecognizer」を入力してConnectボタンを押す。
水色枠のローテーションリコグナイザーも同様にして吹き出しの設定画面を表示させ、Connectionに「Outlet」、Nameに「rotationRecognizer」を入力しConnectボタンを押す。これでピンチリコグナイザーとローテーションリコグナイザーをソースコードから操作できるようになった。
ViewController.swiftを以下のコードに変更する。
2つのリコグナイザーが同じタイミングで動いたときにデリゲートメソッドの「リコグナイザーの同時検知を許可するメソッド」が呼び出されるのでtrueを返して同時検知を許可している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
// // ViewController.swift // import UIKit class ViewController: UIViewController, UIGestureRecognizerDelegate { @IBOutlet weak var testLabel: UILabel! @IBOutlet var rotationRecognizer: UIRotationGestureRecognizer! @IBOutlet var pinchRecognizer: UIPinchGestureRecognizer! //ドラッグ終了時のアフィン変換 var prevEndPinch:CGAffineTransform = CGAffineTransform() var prevEndRotate:CGAffineTransform = CGAffineTransform() //ドラッグ中の前回アフィン変換 var prevPinch:CGAffineTransform = CGAffineTransform() var prevRotate:CGAffineTransform = CGAffineTransform() //最初からあるメソッド override func viewDidLoad() { super.viewDidLoad() //デリゲート先に自分を設定する。 rotationRecognizer.delegate = self pinchRecognizer.delegate = self //アフィン変換の初期値を設定する。 prevEndPinch = testLabel.transform prevEndRotate = testLabel.transform prevPinch = testLabel.transform prevRotate = testLabel.transform } //ピンチ時の呼び出しメソッド @IBAction func pinchLabel(sender: UIPinchGestureRecognizer) { //前回ドラッグ終了時の拡大縮小を引き継いだアフィン変換を行う。 let nowPinch = CGAffineTransformScale(prevEndPinch, sender.scale, sender.scale) //拡大縮小と回転のアフィン変換を合わせたものをラベルに登録する。 testLabel.transform = CGAffineTransformConcat(prevRotate, nowPinch) //今回の拡大縮小のアフィン変換をクラス変数に保存する。 prevPinch = nowPinch if(sender.state == UIGestureRecognizerState.Ended) { //ドラッグ終了時の拡大終了のアフィン変換をクラス変数に保存する。 prevEndPinch = nowPinch } } //回転時の呼び出しメソッド @IBAction func rotateLabel(sender: UIRotationGestureRecognizer) { //前回ドラッグ終了時の回転を引き継いだアフィン変換を行う。 let nowRotate = CGAffineTransformRotate(prevEndRotate, sender.rotation) //拡大縮小と回転のアフィン変換を合わせたものをラベルに登録する。 testLabel.transform = CGAffineTransformConcat(prevPinch, nowRotate) //今回の回転のアフィン変換をクラス変数に保存する。 prevRotate = nowRotate if(sender.state == UIGestureRecognizerState.Ended) { //ドラッグ終了時の回転のアフィン変換をクラス変数に保存する。 prevEndRotate = nowRotate } } //リコグナイザーの同時検知を許可するメソッド func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } } |
2つの指の拡大縮小操作が行われたときは「ピンチ時の呼び出しメソッド」が呼び出され、2つの指の回転操作が行われたときは「回転時の呼び出しメソッド」が呼び出される。
どちらのメソッドでも拡大縮小と回転の両方のアフィン変換を行う必要があるのが注意点である。そのため、「ドラッグ終了時のアフィン変換」と「ドラッグ中のアフィン変換」をクラス変数に持たせている。
ややこしくなってきたので、「ドラッグ終了時のアフィン変換」が必要な理由について回転の具体例を用いて説明する。
部品がどれだけ回転したかは、「回転時の呼び出しメソッド」の引数から知ることができ、その角度は基準点からの角度になる。
例えば、指を瞬時に11度回転させて1回目のメソッド呼び出しで回転角度11度が通知されたとする。そのまま指を画面から離さずに10度回転させて2回目のメソッド呼び出しが行われたときは21度(=11度+10度)が通知される。さらに指を12度回転させて3回目のメソッド呼び出しでは33度(=11度+10度+12度)が通知される。
つまり、ドラッグ中のメソッド呼び出しは前回メソッド呼び出し時からの回転角度が通知されるのではなく、部品の初期状態からの回転角度が通知される。ということだ。
そのため、一度画面から指を離したあとに再回転をさせるときは前回の回転を含めてアフィン変換をする必要がある。しかし、ドラッグ中の回転も含めて前回呼び出し時の回転角を引き継がせると、下図のように回転角が被って通常より大きな回転になってしまう。
そのため、「ドラッグ終了時のアフィン変換」をクラス変数に保存しておき、引き継がせる回転角度を固定させるわけである。
一方、「回転時の呼び出しメソッド」の中で行う拡大縮小のアフィン変換は、ドラッグ中に変化している部品のサイズを反映するために直近の拡大縮小のアフィン変換が必要である。そのため、「ドラッグ中のアフィン変換」をクラス変数に保存し、他のメソッドで利用でるようにしている。
以下は実際のプレイ動画
回転させる部品の中の部品も一緒に回転し、機能してくれるのが嬉しい。