3D Touch専用のカスタムGestureRecognizerの作り方


iPhone6S, 6S Plusで新しく追加された3D Touchの機能を利用するために `class ForceTouchGestureRecognizer: UIGestureRecognizer`を作りました。

そこで、

- 3D Touchについて
- UIGestureRecognizerのサブクラスの作り方

の２つを紹介します。

作ったやつ
https://github.com/milkit/ForceTouchGesture


[公式ドキュメント Adopting 3D Touch on iPhone](
https://developer.apple.com/library/prerelease/ios/documentation/UserExperience/Conceptual/Adopting3DTouchOniPhone/index.html#//apple_ref/doc/uid/TP40016543-CH1-SW1)

# 3D Touch API概要

3D Touchとはスクリーンを押した強さを認識してくれる新しいiPhoneの機能です。公式ドキュメントによると以下の４つに分類されます。

1. HomeScreen quick action
- UIKit peek and pop API
- Web view peek and pop API
- UITouch force properties

今回は4の紹介です。

# UITouch force properties

UITouchクラスに今回追加されたのは2つのプロパティです。

- `var force: CGFloat { get }`
- `var maximumPossibleForce: CGFloat { get }`

名前のままで、`force`でスクリーンを押した強さ、`maximumPossibleForce`でその最大許容値を取得できます。

また、3D Touchが有効かどうかを取得するAPIも公開されています。
**3D Touchはユーザー自身で設定からオフにすることも可能なので、端末から識別できません。**

UITraitCollectionクラスの

```
var forceTouchCapability: UIForceTouchCapability { get }
```

で判別することができます。以下のように定義されています。

```
enum UIForceTouchCapability : Int {
    case Unknown
    case Unavailable
    case Available
}
```

`traitCollection: UITraitCollection`というプロパティがUIViewController, UIView, UIWindow, UIScreenなどにあります。正確に言うと、UITraitEnvironmentプロトコルを採用しているクラスはプロパティを持っています。
　ここで注意点としては、 **例えば、UIViewの`traitCollection`から判別するときに、そのUIViewのインスタンスがビュー階層に追加されていないと、`Unknown`が返ってきます。** ここ要注意です。


# UIGestureRecognizerのサブクラスの作り方

UIGestureRecognizerのサブクラスを作るときはフレームワークをimportする必要があります。Swiftだと以下です。

```
import UIKit.UIGestureRecognizerSubclass
```

そして、サブクラスを作っていきます。

```
public class ForceTouchGestureRecognizer: UIGestureRecognizer {
	
	public private(set) var force: CGFloat = 0
	public private(set) var forceTouchAvailable = false
	public var minimumForce: CGFloat = 3.0
	// 省略
}
```

そして以下4つのメソッドをオーバーライドしてカスタマイズしていきます。

```
public func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent)
public func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent)
public func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent)
public func touchesCancelled(touches: Set<UITouch>, withEvent event: UIEvent)
```

例えば、touchesBeganではジェスチャーを認識したいときは, stateを.Beganにして、それ以外を.Failedにします。

```
override public func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent) {
        
        super.touchesBegan(touches, withEvent: event)
        
        guard let touch = touches.first where self.forceTouchAvailable && touches.count == 1 else {
            
            self.state = .Failed
            return
        }
        
        if #available(iOS 9, *) {
            
            self.minimumForce = min(self.minimumForce, touch.maximumPossibleForce)
            self.force = touch.force
            self.state = .Began
        } else {
            
            self.state = .Failed
        }
    }
```

また例えば、touchesMovedでは`minimumForce`に設定した値を超えた場合はstateを.Endedにしてジェスチャーを終了しています。

```
override public func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent) {
        
        super.touchesMoved(touches, withEvent: event)
        
        if self.state == .Failed {
            
            return
        }
        
        if #available(iOS 9.0, *) {
            
            self.force = touches.first!.force
            
            if self.force > self.minimumForce {
                
                self.state = .Ended
                self.vibrateIfNeeded()
            }
        }
    }
```



# おまけ
iPhone6Sの3D Touchのときに3D Touch専用のバイブレーションがあって、すごく気持ちが良かったので、バイブレーションの機能をつけたいなと思って探しました。しかし、通知時とかの普通のバイブレーションしかなかったです。残念。以下実装方法の紹介です。

```
import AudioToolbox

// バイブレーションしたいタイミングで
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)

```

これだけです。