はじめに
UIGestureRecognizerを使うことでタップやスワイプ、ピンチインといった操作に対応することができるようになります。リファレンスはこちら。これを継承しているクラスとして
- UITapGestureRecognizer
- UIPinchGestureRecognizer
- UIRotationGestureRecognizer
- UISwipeGestureRecognizer
- UIPanGestureRecognizer
- UIScreenEdgePanGestureRecognizer
- UILongPressGestureRecognizer
- UIHoverGestureRecognizer
がありますが、ここではScreenEdgeとHoverとSwipeを除いた5つのクラスを使って以下のような動きを持つサンプルアプリを作成していきます。
準備
メインとなるビューに赤色の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)
}
translation
はUIPanGestureRecognizer
クラスのプロパティで、パン開始時の点を原点とした時の座標を返します。
tansform
はviewの持つCGAffinetransform型のプロパティで、viewに移動などの変換を加える時に使います。viewの持つtransform
をmove
の分動かしたものに置き換えることで、panによる移動を表現できそうです。
しかしこのまま実行すると一度目の移動はうまくいくのですが、二度目以降のpanの際にviewの位置がリセットされてしまいます。
理由はviewの持つtransform
にmove
だけ平行移動する行列をそのまま入れているため、毎度リセットされた状態からスタートしているからです。
座標を引き継ぐ
上述した問題を解決するには、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のtransform
をownTransform
、つまりこれまで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)
}
}