6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

UIGestureRecognizerに入門した

Last updated at Posted at 2021-05-17

はじめに

UIGestureRecognizerを使うことでタップやスワイプ、ピンチインといった操作に対応することができるようになります。リファレンスはこちら。これを継承しているクラスとして

がありますが、ここではScreenEdgeとHoverとSwipeを除いた5つのクラスを使って以下のような動きを持つサンプルアプリを作成していきます。

a9etg-g84nw.gif

準備

メインとなるビューに赤色のSampleViewを貼り付けて挙動を確認します。

import UIKit

class ViewController: UIViewController {

    let sampleView = SampleView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(sampleView)
        // Do any additional setup after loading the view.
    }
}

基本的にはこのSampleViewに変化を加えていきます。

class SampleView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

UITapGestureRecognizer

Tapなので最も基本的なタップ動作です。タップされることで色を変化させています。

AddGestureRecognizerでタップ判定を付与する

tapObject(_ : UITapGestureRecognizer)という関数を指定しています。

override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .red
        addGestureRecognizer((UITapGestureRecognizer(target: self, action: #selector(tapObject(_:)))))
    }

タップされた時に動く関数を作る

背景色をランダムに変えています。

@objc func tappedObject(_ sender: UIHoverGestureRecognizer) {
        self.backgroundColor = UIColor.randomColor
}

randomColorについては以下のように拡張しています。

extension UIColor {
    static var randomColor: UIColor {
        let r = CGFloat.random(in: 0 ... 255) / 255.0
        let g = CGFloat.random(in: 0 ... 255) / 255.0
        let b = CGFloat.random(in: 0 ... 255) / 255.0
        return UIColor(red: r, green: g, blue: b, alpha: 1.0)
    }
}

これでSampleViewをタップされることで色が変化するViewにすることができました。

UIPanGestureRecognizer

panは動詞でゆっくりと左右に動くという意味のようです。

AddGestureRecognizerでtap判定を付与する

addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panObject(_:))))

panを判定したときに動く関数を作る

panされた時にその距離だけsampleViewを動かす操作を実装します。

@objc func panObject(_ sender: UIPanGestureRecognizer) {
        self.transform = CGAffineTransform(translationX: sender.translation(in: self).x, y: sender.translation(in: self).y)
}

translationUIPanGestureRecognizerクラスのプロパティで、パン開始時の点を原点とした時の座標を返します。

tansformはviewの持つCGAffinetransform型のプロパティで、viewに移動などの変換を加える時に使います。viewの持つtransformmoveの分動かしたものに置き換えることで、panによる移動を表現できそうです。

しかしこのまま実行すると一度目の移動はうまくいくのですが、二度目以降のpanの際にviewの位置がリセットされてしまいます。

理由はviewの持つtransformmoveだけ平行移動する行列をそのまま入れているため、毎度リセットされた状態からスタートしているからです。

座標を引き継ぐ

上述した問題を解決するには、viewの持つコマンドの中でviewの持つtransformをコピーしておく役割を持つプロパティを作成する必要があります。

@objc func panObject(_ sender: UIPanGestureRecognizer) {
        if sender.state == .began {
            ownTransform = self.transform
        }
        self.transform = ownTransform.translatedBy(x: sender.translation(in: self).x, y: sender.translation(in: self).y)
    }

stateはUIGestureRecognizerが持つプロパティで、ジェスチャがどのようなものなのかということを返します。そのstate.began、つまりジェスチャが開始された時に、ownTransformに自身のviewのtransformをコピーしておきます。

そして、viewのtransformownTransform、つまりこれまでviewがされた変換の情報を持つCGAffineTransformにさらに平行移動を施しています。

UIPinchGestureRecognizer

AddGestureRecognizerでpinch判定を付与する

addGestureRecognizer((UITapGestureRecognizer(target: self, action: #selector(tapObject(_:)))))

pinchされた時に動く関数を作る

ピンチインで拡大、ピンチアウトで縮小を表現していきます。

@objc func pinchObject(_ sender: UIPinchGestureRecognizer) {
        if sender.state == .began {
            ownTransform = self.transform
        }
        self.transform = ownTransform.scaledBy(x: sender.scale, y: sender.scale)
}

scaleは$$\frac{ピンチ中の指の幅タップされた時の幅}{ピンチ開始時の指の幅}$$です。

ここでも先程のパンによる平行移動と同様にownTransformプロパティを使用しています。

UIRotateGestureRecognizer

AddGestureRecognizerでrotate判定を付与する

addGestureRecognizer(UIRotationGestureRecognizer(target: self, action: #selector(rotateObject(_:))))

rotateを判定した時に動く関数を作る

二本指でタップされ回転された時、それに合わせてViewを回転させていきます。ここでも先程定義したownTransformプロパティを使用しています。

@objc func rotateObject(_ sender: UIRotationGestureRecognizer) {
        let rotation = sender.rotation
        if sender.state == .began {
            ownTransform = self.transform
        }
        self.transform = ownTransform.rotated(by: rotation)
}

rotationはタップされた時の二本指を通る直線と現在の二本指を通る直線のなす角です。ここでも先程までと同様に、rotateが始まった時にownTransformに自身のtransformをコピーし、その後そのコピーに回転を加える作用を施したものをviewのtranslationとしています。

UILongPressGestureRecognizer

AddGestureRecognizerでlongpress判定を付与する

addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(longPressedObject(_:))))

longpressを判定した時動く関数を作る

0.5秒かけて2倍に拡大するアニメーションを作成しています。

@objc func longPressedObject(_ :UILongPressGestureRecognizer) {
        UIView.animate(withDuration: 0.5, delay: 1.0, animations: {
            self.transform = CGAffineTransform(scaleX: 2, y: 2)
        })
}

ソースコード

ファイルを分けていないのでコピペで動きます。

import UIKit

class ViewController: UIViewController {

    let sampleView = SampleView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(sampleView)
        // Do any additional setup after loading the view.
    }
}

class SampleView: UIView {
    var ownTransform: CGAffineTransform!
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .red
        addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panObject(_:))))
        addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(pinchObject)))
        addGestureRecognizer(UIRotationGestureRecognizer(target: self, action: #selector(rotateObject(_:))))
        addGestureRecognizer((UITapGestureRecognizer(target: self, action: #selector(tapObject(_:)))))
        addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(longPressedObject(_:))))
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    @objc func panObject(_ sender: UIPanGestureRecognizer) {
        if sender.state == .began {
            ownTransform = self.transform
        }
        self.transform = ownTransform.translatedBy(x: sender.translation(in: self).x, y: sender.translation(in: self).y)
    }

    @objc func tapObject(_ sender: UITapGestureRecognizer) {
        self.backgroundColor = UIColor.randomColor
    }

    @objc func rotateObject(_ sender: UIRotationGestureRecognizer) {
        let rotation = sender.rotation
        if sender.state == .began {
            ownTransform = self.transform
        }
        self.transform = ownTransform.rotated(by: rotation)
    }
    @objc func pinchObject(_ sender: UIPinchGestureRecognizer) {
        if sender.state == .began {
            ownTransform = self.transform
        }
        self.transform = ownTransform.scaledBy(x: sender.scale, y: sender.scale)
    }
    @objc func longPressedObject(_ :UILongPressGestureRecognizer) {
        UIView.animate(withDuration: 0.5, delay: 1.0, animations: {
            self.transform = CGAffineTransform(scaleX: 2, y: 2)
        })
    }
}
extension UIColor {
    static var randomColor: UIColor {
        let r = CGFloat.random(in: 0 ... 255) / 255.0
        let g = CGFloat.random(in: 0 ... 255) / 255.0
        let b = CGFloat.random(in: 0 ... 255) / 255.0
        return UIColor(red: r, green: g, blue: b, alpha: 1.0)
    }
}
6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?