【Swift】Gesture Recognizerの合わせ技。部品の拡大縮小、回転、移動を1ドラッグで出来るようにする。(Swift 2.1、XCode 7.2)
部品の拡大縮小、回転、移動
過去の記事で、ローテーションリコグナイザーとピンチリコグナイザーの2つを使って、部品の回転と拡大縮小を同時に行う方法を説明した。
本記事では、上記にPan Gesture Recognizer(以下、パンリコグナイザー)を追加して、部品の拡大縮小、回転、移動を1ドラッグの中で行うための実装方法を説明する。具体的には以下のアクションが1ドラッグの中で行えるようになる。
部品の上に置いた2本の指の間隔を広げると部品が拡大し、狭めると部品が縮小する。
部品の上に置いた2本の指を結ぶ直線を回転させると、部品も同じ方向に回転する。
部品の上に置いた2本の指の距離を一定に保ちながら並行移動すると、部品も同じ方向に移動する。
以降の手順は部品の回転と拡大縮小を同時に行う記事の続きからなので、実装を試してみる人は先に読んでおくことをお勧めする。⇒「記事」
パンリコグナイザーを追加する
パンリコグナイザーをラベルに配置する(下図赤矢印)。黄緑枠のアシスタントエディタボタンを押してViewController.swiftを開く。Ctrlキーを押しながら紫枠のパンリコグナイザーをドラッグ&ドロップでソースコードまで運んで吹き出しの設定画面を表示する。
Connectionに「Action」、Nameに「panLabel」を入力し、Connectボタンを押す。これでパンリコグナイザーのイベントをソースコードで受けれるようになった。
同じようにして、もう一度パンリコグナイザーの吹き出しの設定画面を表示し、Connectionに「Outlet」、Nameに「panRecognizer」、Typeに「UIPanGestureRecognizer」を入力し、Connectボタンを押す。これでパンリコグナイザーをソースコードから操作できるようになった。
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 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 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
// // ViewController.swift // import UIKit class ViewController: UIViewController, UIGestureRecognizerDelegate { @IBOutlet weak var testLabel: UILabel! @IBOutlet var rotationRecognizer: UIRotationGestureRecognizer! @IBOutlet var pinchRecognizer: UIPinchGestureRecognizer! @IBOutlet var panRecognizer: UIPanGestureRecognizer! //ドラッグ終了時のアフィン変換 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 panRecognizer.delegate = self //アフィン変換の初期値を設定する。 prevEndPinch = testLabel.transform prevEndRotate = testLabel.transform prevPinch = testLabel.transform prevRotate = testLabel.transform } //ドラッグ時の呼び出しメソッド @IBAction func panLabel(sender: UIPanGestureRecognizer) { //移動量を取得する。 let move:CGPoint = sender.translationInView(view) //ドラッグした部品の座標に移動量を加算する。 sender.view!.center.x += move.x sender.view!.center.y += move.y //移動量を0にする。 sender.setTranslation(CGPointZero, inView:view) } //ピンチ時の呼び出しメソッド @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 } } |
以下は実際のプレイ動画
並行移動にアフィン変換を使う
上記コードの移動は、部品の座標に移動量を加算する方法をとったので、拡大縮小と回転のアフィン変換を使ったやり方と手法が異なる。アフィン変換には並行移動もあるので、拡大縮小と回転の手法と合わせてすべてアフィン変換で実装できる。
具体的にはViewController.swiftを以下のコードのように変更する。拡大縮小、回転のアフィン変換については次の記事も参考にされたし。⇒「記事」
ちなみに、回転、並行移動、拡大縮小を適用するアフィン変換は[回転の行列][並行移動の行列][拡大縮小の行列]の結果になるので、プログラムのCGAffineTransformConcatメソッドの引数の順番を変えると別の変換結果になるので利用の際は注意すること。
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 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
// // ViewController.swift // import UIKit class ViewController: UIViewController, UIGestureRecognizerDelegate { @IBOutlet weak var testLabel: UILabel! @IBOutlet var rotationRecognizer: UIRotationGestureRecognizer! @IBOutlet var pinchRecognizer: UIPinchGestureRecognizer! @IBOutlet var panRecognizer: UIPanGestureRecognizer! //ドラッグ終了時のアフィン変換 var prevEndPinch:CGAffineTransform = CGAffineTransform() var prevEndRotate:CGAffineTransform = CGAffineTransform() var prevEndMove:CGAffineTransform = CGAffineTransform() //ドラッグ中の前回アフィン変換 var prevPinch:CGAffineTransform = CGAffineTransform() var prevRotate:CGAffineTransform = CGAffineTransform() var prevMove:CGAffineTransform = CGAffineTransform() //最初からあるメソッド override func viewDidLoad() { super.viewDidLoad() //デリゲート先に自分を設定する。 rotationRecognizer.delegate = self pinchRecognizer.delegate = self panRecognizer.delegate = self //ドラッグするには2本の指が必要にする。 panRecognizer.minimumNumberOfTouches = 2 //アフィン変換の初期値を設定する。 prevEndPinch = testLabel.transform prevEndRotate = testLabel.transform prevEndMove = testLabel.transform prevPinch = testLabel.transform prevRotate = testLabel.transform prevMove = testLabel.transform } //ドラッグ時の呼び出しメソッド @IBAction func panLabel(sender: UIPanGestureRecognizer) { //移動量を取得する。 let move:CGPoint = sender.translationInView(view) //前回ドラッグ終了時の並行移動を引き継いだアフィン変換を作る。 let nowMove = CGAffineTransformTranslate(prevEndMove, move.x, move.y) //拡大縮小と並行移動のアフィン変換を合わせる。 let moveAndPinch = CGAffineTransformConcat(prevPinch,nowMove) //さらに回転のアフィン変換と合わせたものをラベルに登録する。 testLabel.transform = CGAffineTransformConcat(prevRotate,moveAndPinch) //今回の並行移動のアフィン変換をクラス変数に保存する。 prevMove = nowMove if(sender.state == UIGestureRecognizerState.Ended) { //ドラッグ終了時の並行移動のアフィン変換をクラス変数に保存する。 prevEndMove = nowMove } } //ピンチ時の呼び出しメソッド @IBAction func pinchLabel(sender: UIPinchGestureRecognizer) { //前回ドラッグ終了時の拡大縮小を引き継いだアフィン変換を作る。 let nowPinch = CGAffineTransformScale(prevEndPinch, sender.scale, sender.scale) //拡大縮小と並行移動のアフィン変換を合わせる。 let moveAndPinch = CGAffineTransformConcat(nowPinch,prevMove) //さらに回転のアフィン変換と合わせたものをラベルに登録する。 testLabel.transform = CGAffineTransformConcat(prevRotate,moveAndPinch) //今回の拡大縮小のアフィン変換をクラス変数に保存する。 prevPinch = nowPinch if(sender.state == UIGestureRecognizerState.Ended) { //ドラッグ終了時の拡大縮小のアフィン変換をクラス変数に保存する。 prevEndPinch = nowPinch } } //回転時の呼び出しメソッド @IBAction func rotateLabel(sender: UIRotationGestureRecognizer) { //前回ドラッグ終了時の回転を引き継いだアフィン変換を作る。 let nowRotate = CGAffineTransformRotate(prevEndRotate, sender.rotation) //拡大縮小と並行移動のアフィン変換を合わせる。 let moveAndPinch = CGAffineTransformConcat(prevPinch, prevMove) //さらに回転のアフィン変換と合わせたものをラベルに登録する。 testLabel.transform = CGAffineTransformConcat(nowRotate,moveAndPinch) //今回の回転のアフィン変換をクラス変数に保存する。 prevRotate = nowRotate if(sender.state == UIGestureRecognizerState.Ended) { //ドラッグ終了時の回転のアフィン変換をクラス変数に保存する。 prevEndRotate = nowRotate } } //リコグナイザーの同時検知を許可するメソッド func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } } |
以下は実際のプレイ動画