【Swift】UIGestureRecognizerとは?UIKitでタップイベントを取得する方法!
この記事からわかること
- SwiftのUIKitでタップイベントを取得する方法
- ジェスチャーやジェスチャーレコグナイザーとは?
- UIGestureRecognizerクラスの使用方法
- UITapGestureRecognizer/UILongPressGestureRecognizerの使用方法
- 長押し/パン/ピンチの操作方法
- UIViewをサイズ変更させたり移動させるには?
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
SwiftのUIKitでタップや長押しなどのイベントをビューに追加する方法をまとめました。
Gesture(ジェスチャー)とは?
Web制作では「イベント」と呼ばれるクリックやホバーなどのユーザー操作ですがSwiftではGesture(ジェスチャー)と呼ばれ管理されています。
ジェスチャーにはタップや長押し、スワイプ、ピンチなど、スマホを操作する際に発生するイベントが含まれています。
Swiftではジェスチャーが発生した時に任意の処理を行わせる仕組みが備わっているのです。これは元々イベント処理を持っているボタンなどだけではなく、Textやラベルなど本来イベント処理を保持していないビューに対してもカスタマイズして組み込むことが可能になります。
ジェスチャーに対する処理の組み込みはSwift UIではモディファイア(メソッド)として、UIKitではGestureRecognizer(ジェスチャーレコグナイザー)クラスとして用意されています。しかし両者は別物なので取扱には注意してください。
UIGestureRecognizerクラス
公式リファレンス:UIGestureRecognizerクラス
@MainActor class UIGestureRecognizer : NSObject
UIKitのジェスチャー管理の大元はUIGestureRecognizer
クラスです。このクラスを継承してタップを検知するUITapGestureRecognizer
やピンチを検知するUIPinchGestureRecognizer
などが定義されています。
ちなみに「Recognizer」とは「認識者」という意味です。
UIGestureRecognizerクラスにはデリゲートを使用するためのdelegate
プロパティやジェスチャージェスチャレコグナイザーの現在の状態を示すstateプロパティ、以下のような引数を保持するイニシャライザなどを保持しています。
init(
target: Any?,
action: Selector?
)
ジェスチャーレコグナイザーの種類
UIGestureRecognizer
クラスを継承したサブクラスとしてさまざまな種類のジェスチャーレコグナイザーが用意されています。
クラス | 概要 |
---|---|
UITapGestureRecognizer | タップ |
UIPinchGestureRecognizer | ピンチ |
UIRotationGestureRecognizer | 画面回転 |
UISwipeGestureRecognizer | スワイプ(※) |
UIPanGestureRecognizer | パン(※) |
UIScreenEdgePanGestureRecognizer | 画面の端付近で始まるパン |
UILongPressGestureRecognizer | 長押し |
UIHoverGestureRecognizer | ホバー |
※:スワイプとパンは似たような動作(画面を指でスライドさせる)を表します。正確にはスワイプはタップ地点から直線方向のスライド、パンはタップ地点からあらゆる方向のスライドを検知するようです。スワイプレコグナイザーとパンレコグナイザーは競合してしまうため取り扱いには注意が必要です。
ジェスチャーの登録方法
ジェスチャーはビューに対して登録する形で使用していきます。ここでいうビューとはUIButton
やUILabel
といった全てのビュークラスです。
公式リファレンス:addGestureRecognizerメソッド
func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer)
UIKitのビュークラスの大元であるUIView
クラスが持つaddGestureRecognizer
メソッドを使用することでそのビューに対してジェスチャーを登録することができます。
引数には登録したいUIGestureRecognizerクラスを渡します。
ジェスチャーの登録の流れ
- 動作させたいジェスチャレコグナイザークラスを定義
- ジェスチャー発生時に実行する処理を定義
- 実装させるビューからaddGestureRecognizerメソッドを呼び出して紐付ける
タップ:UITapGestureRecognizer
まずはUITapGestureRecognizer
使用してタップされた時の処理を実装をしていきます。まずはサンプル用のUIViewControllerクラスを用意しておきます。
import UIKit
class GestureViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
1.動作させたいジェスチャレコグナイザークラスを定義
まずはUITapGestureRecognizer
インスタンスを生成します。引数にはターゲットとなるオブジェクト(今回はself)とイベント発生時に実行させたいメソッド名を#selectorを使用して指定します。Objective-Cの仕組みである#selector
で指定するメソッド側には@objc
の付与が必要になります。
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapped(_:)))
}
おすすめ記事:【Swift UIKit】#selectorとは?使い方と@objcとsenderの意味まとめ
2.ジェスチャー発生時に実行する処理を定義
続いて実行時のメソッドを定義していきます。メソッドの定義前には@objc
の付与し、引数には指定したUIGestureRecognizer
型を受け取るようにしておきます。今回はタップイベント時のメソッドなのでメソッド名はtapped
にしておきました。
@objc func tapped(_ sender : UITapGestureRecognizer) {
print("タップされたよ")
}
3.Viewとの紐付け
最後に定義したGestureRecognizerインスタンスをView
と紐付けていきます。今回はボタンではなくUIViewに紐付けてみたいと思います。シミュレーターでビルドしても何も表示されませんがタップすると処理が実行されるのを試してみてください。
self.view
からaddGestureRecognizer
メソッドを呼び出し、引数に先ほど定義したインスタンスを渡します。これで実装は完了です。
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapped(_:)))
self.view.addGestureRecognizer(tapGesture)
}
このようにUIGestureRecognizer
を使ったジェスチャー検知はView側への追加とメソッドの定義だけで簡単に実装できます。ここからは他のジェスチャー処理も見ていきます。
import UIKit
class GestureViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapped(_:)))
self.view.addGestureRecognizer(tapGesture)
}
@objc func tapped(_ sender : UITapGestureRecognizer) {
print("タップされたよ")
}
}
長押し:UILongPressGestureRecognizer
長押しされた時の処理を実装するにはUILongPressGestureRecognizer
を使用します。先ほどのようにインスタンスの生成とビューへの追加、処理メソッドを定義すればOKです。
let longTapGesture = UILongPressGestureRecognizer(target: self, action: #selector(longTapped(_:)))
self.view.addGestureRecognizer(longTapGesture)
@objc func longTapped(_ sender : UILongPressGestureRecognizer) {
print("長押しされたよ")
}
これで長押し時に処理が実行されるようになりました。
ピンチ:UIPinchGestureRecognizer
ピンチされた時に処理を実行させるにはUIPinchGestureRecognizer
を使用します。次は新たにUIViewプロパティを定義してピンチされた時にそのサイズを変更させてみます。ピンチ処理の登録方法は今までと同じです。
class GestureViewController: UIViewController {
let pinchView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
pinchView.frame = CGRect(x: 0, y:0, width: UIScreen.main.bounds.width / 2 , height: 100)
pinchView.backgroundColor = .orange
self.pinchView.center = self.view.center
self.view.addSubview(pinchView)
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinched(_:)))
pinchView.addGestureRecognizer(pinchGesture)
}
}
ピンチされたViewを拡大/縮小させる機能
ピンチされたViewを拡大/縮小させる機能を実装するには場合は以下のように変換情報(アフィン変換※)を保持するtransform
プロパティを操作します。
※:アフィン変換とは図形を引き伸ばしたり回転させたりすること。
@objc func pinched(_ sender: UIPinchGestureRecognizer) {
self.pinchView.transform = self.pinchView.transform.scaledBy(x: sender.scale, y: sender.scale)
}
UIView.transform
self.pinchView.transform = self.pinchView.transform.scaledBy(x: sender.scale, y: sender.scale)
Viewを実際に変化させるのは上記の部分です。UIViewのいくつかのプロパティは値を変化させることでアニメーションが可能になっておりtransform
もその1つです。transform
プロパティが準拠しているCGAffineTransform
構造体の持つscaledBy
メソッドを呼び出してViewに格納しスケールを変化させます。
パン:UIPanGestureRecognizer
パン(指をスライド)させた時に処理を実装させるにはUIPanGestureRecognizer
を使用します。例えばビューパンした位置へ移動させるには以下のように実装します。
var initialCenter = CGPoint()
let panView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// MARK: - UIView
panView.frame = CGRect(x: 0, y:0, width: UIScreen.main.bounds.width / 2 , height: 100)
panView.backgroundColor = .orange
self.panView.center = self.view.center
self.view.addSubview(panView)
// MARK: - パンイイベント
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panSlide(_:)))
panView.addGestureRecognizer(panGesture)
}
// MARK: - パン
@objc func panSlide(_ sender: UIPanGestureRecognizer) {
// 変化した位置情報を取得
let translation: CGPoint = sender.translation(in: view)
// 開始位置を保持
if sender.state == .began {
initialCenter = panView.center
}
let newCenterX = initialCenter.x + translation.x
let newCenterY = initialCenter.y + translation.y
// 新しい位置にビューを配置
panView.center = CGPoint(x: newCenterX, y: newCenterY)
}
transform(in:)
メソッドで変化した位置量の情報を取得して初期位置と計算しcenter
に設定することでビューを配置し直しています。
UIGestureRecognizer.State
UIGestureRecognizerクラスのStateプロパティはジェスチャージェスチャレコグナイザーの現在の状態を取得できます。
enum State : Int, @unchecked Sendable{
case possible
// デフォルトの状態
case began
// 連続したジェスチャーの受信
case changed
// 連続したジェスチャーの変更
case ended
// 連続したジェスチャーの終わり→UIGesture.State.possibleに戻る
case cancelled
// 連続したジェスチャーのキャンセル→UIGesture.State.possibleに戻る
case failed
// ジェスチャーとして認識失敗→UIGesture.State.possibleに戻る
}
// ライフサイクル
Possible ----> Began ----> [Changed] ----> Cancelled
Possible ----> Began ----> [Changed] ----> Ended
全体のコード
Storyboardは使用していないのでそのままコピペするだけで動作します。
import UIKit
class GestureViewController: UIViewController {
let pinchView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// MARK: - タップ
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapped(_:)))
self.view.addGestureRecognizer(tapGesture)
// MARK: - 長押し
let longTapGesture = UILongPressGestureRecognizer(target: self, action: #selector(longTapped(_:)))
self.view.addGestureRecognizer(longTapGesture)
// MARK: - UIView
pinchView.frame = CGRect(x: 0, y:0, width: UIScreen.main.bounds.width / 2 , height: 100)
pinchView.backgroundColor = .orange
self.pinchView.center = self.view.center
self.view.addSubview(pinchView)
// MARK: - ピンチ
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinched(_:)))
pinchView.addGestureRecognizer(pinchGesture)
// MARK: - パン
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panSlide(_:)))
pinchView.addGestureRecognizer(panGesture)
}
// MARK: - タップ
@objc func tapped(_ sender : UITapGestureRecognizer) {
print("タップされたよ")
}
// MARK: - 長押し
@objc func longTapped(_ sender : UILongPressGestureRecognizer) {
print("長押しされたよ")
}
// MARK: - ピンチ
@objc func pinched(_ sender: UIPinchGestureRecognizer) {
self.pinchView.transform = self.pinchView.transform.scaledBy(x: sender.scale, y: sender.scale)
}
// MARK: - パン
@objc func panSlide(_ sender: UIPanGestureRecognizer) {
self.pinchView.transform = CGAffineTransform(translationX: sender.translation(in: pinchView).x, y: sender.translation(in: pinchView).y)
}
}
おすすめ記事:【Swift/UIKit】コードからビューのタップイベントを発行する方法!sendActions
おすすめ記事:【Swift/UIKit】タップした座標(位置)を取得する方法!touchesBegan
おすすめ記事:【Swift/UIKit】UIViewでタップイベントなどを無効にする方法!isUserInteractionEnabled
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。