はじめに
例えば、長押し処理の例...
アプリを作成する上で、Buttonに対して長押し時の処理を実装したいと思ったら、UILongPressGestureRecognizerを使用するのが定石です。
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(self.onLongPressed(_:)))
button.tag = 123
button.addGestureRecognizer(longPress)
@objc func onLongPressed(_ gesture: UILongPressGestureRecognizer) {
guard let sender = gesture.view as? UIButton else {
print("Sender is not a button")
return
}
switch (gesture.state) {
case .began:
print("longPress start")
case .ended:
print("longPress end")
default:
break
}
// 長押しされたボタンは sender.tag で判別
}
ただ、画面にボタンがたくさんあって、全部に長押し処理をつけなきゃいけない場合、Recognizerを必要数分用意して、handleするメソッドも分岐の嵐になるので、見た目が美しくありません。
UIViewを拡張してみた
closureで書けるようにしてみました。
UIView+GestureClosure.swift
import UIKit
class GestureClosureSleeve<T: UIGestureRecognizer> {
let closure: (_ gesture: T)->()
init(_ closure: @escaping (_ gesture: T)->()) {
self.closure = closure
}
@objc func invoke(_ gesture: Any) {
guard let gesture = gesture as? T else { return }
closure(gesture)
}
}
extension UIView {
func longPress(duration: CFTimeInterval, _ closure: @escaping (_ gesture: UILongPressGestureRecognizer)->()) {
let sleeve = GestureClosureSleeve<UILongPressGestureRecognizer>(closure)
let recognizer = UILongPressGestureRecognizer(target: sleeve, action: #selector(GestureClosureSleeve.invoke(_:)))
recognizer.minimumPressDuration = duration
self.addGestureRecognizer(recognizer)
objc_setAssociatedObject(self, String(format: "[%d]", arc4random()), sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
こんな感じで使えます。
button.longPress(duration: 0.1) { (gesture) in
switch (gesture.state) {
case .began:
print("longPress start")
case .ended:
print("longPress end")
default:
break
}
}
(2018/07/16 追記)
PanGesture, PinchGestureにも対応できることが確認できました。
extension UIView {
func pan(_ closure: @escaping (_ gesture: UIPanGestureRecognizer)->()) {
let sleeve = GestureClosureSleeve<UIPanGestureRecognizer>(closure)
let recognizer = UIPanGestureRecognizer(target: sleeve, action: #selector(GestureClosureSleeve.invoke(_:)))
self.addGestureRecognizer(recognizer)
objc_setAssociatedObject(self, String(format: "[%d]", arc4random()), sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
func pinch(_ closure: @escaping (_ gesture: UIPinchGestureRecognizer)->()) {
let sleeve = GestureClosureSleeve<UIPinchGestureRecognizer>(closure)
let recognizer = UIPinchGestureRecognizer(target: sleeve, action: #selector(GestureClosureSleeve.invoke(_:)))
self.addGestureRecognizer(recognizer)
objc_setAssociatedObject(self, String(format: "[%d]", arc4random()), sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
こんな感じで使用できます。
someView.pan { (gesture) in
switch (gesture.state) {
case .began:
print("pan start")
case .changed:
print("pan changed")
case .ended:
print("pan end")
default:
break
}
// タップ開始地点からの移動量を元に、方向を判別するサンプル
/*
let position = gesture.translation(in: self.view)
if abs(position.x) > abs(position.y) {
if position.x > 0 {
// right
} else {
// left
}
} else {
if position.y > 0 {
// down
} else {
// up
}
}
*/
}
someView.pinch { (gesture) in
switch (gesture.state) {
case .began:
print("pinch start (scale = \(gesture.scale)")
case .changed:
print("changed (scale = \(gesture.scale)")
case .ended:
print("pinch end (scale = \(gesture.scale)")
default:
break
}
}
他のGestureも気軽に実装することができそうですね。
おわりに
UIControlの操作をclosureで書ける例は、以下のサイトの例を参考にさせてもらっています。
https://stackoverflow.com/questions/25919472/adding-a-closure-as-target-to-a-uibutton
AndroidのonClickListenerの感覚で書けるので、個人的に重宝しています。
どなたかの参考になれば幸いです。