UIViewに各種Gesture処理を簡単に実装するExtension

# はじめに

例えば、長押し処理の例...

アプリを作成する上で、Buttonに対して長押し時の処理を実装したいと思ったら、UILongPressGestureRecognizerを使用するのが定石です。

```swift
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(self.onLongPressed(_:)))
button.tag = 123
button.addGestureRecognizer(longPress)
```
```swift
@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)
    }
}
```

こんな感じで使えます。

```swift
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にも対応できることが確認できました。

```swift
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)
    }
}
```

こんな感じで使用できます。

```swift
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の感覚で書けるので、個人的に重宝しています。

どなたかの参考になれば幸いです。