【Swift】Pan Gesture Recognizerの使い方。ドラッグ&ドロップを検知する。(Swift 2.1、XCode 7.2)
Pan Gesture Recognizerとは
本記事ではSwiftで使える部品のPan Gesture Recognizer(以下、パンリコグナイザー)について説明する。
パンリコグナイザーとは、ドラッグ&ドロップを検知する部品である。前回記事のスワイプリコグナイザーは素早く指を動かさなければ検知されないが、パンリコグナイザーはゆっくり指を動かしても検知され、元の位置からの移動量を正確に把握することができる。⇒「スワイプリコグナイザーの記事」
部品をドラッグ&ドロップする。
ドラッグ&ドロップでイメージするものといえば部品をつかんで運ぶ動作だ。パンリコグナイザーを使って実装してみよう。
デバイス画面にラベルを配置する(下図赤矢印)。紫枠のアトリビュートインスペクタボタンを押して設定画面を表示する。Textに「テスト」、Colorに白、Backgroundに緑、User Interaction Enabledにチェックを入れる。
黄緑枠のアシスタントエディタボタンを押してViewController.swiftを開く。Ctrlキーを押しながらラベルをドラッグ&ドロップでソースコードまで運んで吹き出しの設定画面を表示する。Connectionに「Outlet」、Nameに「testLabel」を入力してConnectボタンを押す。これで、ソースコードでラベルを操作できるようになった。
パンリコグナイザーをラベルに配置する(下図赤矢印)。Ctrlキーを押しながら水色枠のパンリコグナイザーをドラッグ&ドロップでソースコードまで運んで吹き出しの設定画面を表示させる。
Connectionに「Action」、Nameに「panLabel」、Typeに「UIPanGestureRecognizer」を設定してConnectボタンを押す。これでラベルをドラッグ&ドロップしたときのイベントをソースコードで受けれるようになった。
ViewController.swiftを以下のコードに変更する。
「ドラッグ時の呼び出しメソッド」が指を移動するたびに呼び出されるので、引数から移動量を取得してラベルの座標に加算している。移動量は蓄積する値なので0にしてからメソッドを抜けることで次の呼び出しに備えている。
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 |
// // ViewController.swift // import UIKit class ViewController: UIViewController { @IBOutlet weak var testLabel: UILabel! //最初からあるメソッド override func viewDidLoad() { super.viewDidLoad() } //ドラッグ時の呼び出しメソッド @IBAction func panLabel(sender: UIPanGestureRecognizer) { //移動量を取得する。 let move:CGPoint = sender.translationInView(view) //ドラッグした部品の座標に移動量を加算する。 sender.view!.center.x += move.x sender.view!.center.y += move.y //ラベルに現在座標を表示する。 //testLabel.text = "\(sender.view!.frame.origin.x), \(sender.view!.frame.origin.y)" //移動量を0にする。 sender.setTranslation(CGPointZero, inView:view) } } |
以下は実際のプレイ動画
ラベルを更新すると元の位置に戻ってしまう
上記コードでは1つ不思議な事象が発生する。ラベルに移動量を表示する処理(コメントアウト箇所)を入れると、以下の動画のようにラベルが元の位置にすぐに戻ってしまうのだ。
解消方法は、AutoLayoutを使って部品に位置の制約を追加し、制約の更新で部品を移動する作りにすることである。実際にやってみよう。
下図赤枠のAlignボタンを押して吹き出しの設定画面を表示させる。Horizontally in Container と Vertically in Containerにチェックを入れ、両方とも「0」を入力して「Add 2 Constraints」ボタンを押す。これで、どんな画面サイズでも、アプリ起動時のラベルは必ず画面中央に配置されるようになった。
下図紫枠のPinボタンを押して吹き出しの設定画面を表示させる。HeightとWidthにチェックを入れて、「Add 2 Constraints」ボタンを押す。これでラベルのサイズが固定された。
下図紫枠のアシスタントエディタボタンを押してVeiwController.swiftを表示する。
Ctrlキーを押しながら赤枠のパンリコグナイザー(Test Label.centerX = centerX)をドラッグ&ドロップでソースコードまで運んで吹き出しの設定画面を表示させる。Connectionに「Outlet」、Nameに「xConstraint」を入力し、Connectボタンを押す。
黄緑枠のパンリコグナイザー(Test Label.centerY = centerY)も同じようにして吹き出しの設定画面を表示し、Connectionに「Outlet」、Nameに「yConstraint」を入力し、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 |
// // ViewController.swift // import UIKit class ViewController: UIViewController { @IBOutlet weak var testLabel: UILabel! @IBOutlet weak var xConstraint: NSLayoutConstraint! @IBOutlet weak var yConstraint: NSLayoutConstraint! //最初からあるメソッド override func viewDidLoad() { super.viewDidLoad() } //ドラッグ時の呼び出しメソッド @IBAction func panLabel(sender: UIPanGestureRecognizer) { //移動量を取得する。 let move:CGPoint = sender.translationInView(view) //ラベルの位置の制約に移動量を加算する。 xConstraint.constant += move.x yConstraint.constant += move.y //画面表示を更新する。 view.layoutIfNeeded() //ラベルに現在座標を表示する。 testLabel.text = "\(sender.view!.frame.origin.x), \(sender.view!.frame.origin.y)" //移動量を0にする。 sender.setTranslation(CGPointZero, inView:view) } } |
以下は実際のプレイ動画。ラベルの表示を更新しても元の位置に戻らなくなった。