【Swift】UIGestureRecognizerとは?UIKitでタップイベントを取得する方法!

【Swift】UIGestureRecognizerとは?UIKitでタップイベントを取得する方法!

この記事からわかること

  • SwiftUIKitタップイベント取得する方法
  • ジェスチャージェスチャーレコグナイザーとは?
  • 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 ホバー

※:スワイプとパンは似たような動作(画面を指でスライドさせる)を表します。正確にはスワイプはタップ地点から直線方向のスライドパンはタップ地点からあらゆる方向のスライドを検知するようです。スワイプレコグナイザーとパンレコグナイザーは競合してしまうため取り扱いには注意が必要です。

ジェスチャーの登録方法

ジェスチャーはビューに対して登録する形で使用していきます。ここでいうビューとはUIButtonUILabelといった全てのビュークラスです。

公式リファレンス:addGestureRecognizerメソッド

func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer)

UIKitのビュークラスの大元であるUIViewクラスが持つaddGestureRecognizerメソッドを使用することでそのビューに対してジェスチャーを登録することができます。

引数には登録したいUIGestureRecognizerクラスを渡します。

ジェスチャーの登録の流れ

  1. 動作させたいジェスチャレコグナイザークラスを定義
  2. ジェスチャー発生時に実行する処理を定義
  3. 実装させるビューから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)
    }
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index