Force Touch(3D Touch)のZ軸方向への移動は-touchesMoved:withEvent:を呼びだす。


ForceTapGestureRecognizerは[ForceTouchGestureRecognizer](https://github.com/yusuga/ForceTouchGestureRecognizer)に名前を変更しました。

---

発端は[SloppySwiper](https://github.com/fastred/SloppySwiper)がiPhone6sで高頻度で動作しなくなりました。これはシミュレータでは発生せずどうやらiPhone6sのみ(6s+では検証できてませんがおそらく同等)で起きる現象でした。iOS9のiPhone4sでは発生しないのでiOS9.xの問題でもなさそうです。

[SloppySwiper](https://github.com/fastred/SloppySwiper)は`UINavigationController`で利用可能な左エッジスワイプでpopするのを画面内のどこからでも利用できるようにした、`Interactive Transitions`のアニメーターです。
本来だったら下のgifの様に右方向にドラッグすると`Interactive Transitions`が開始され1つ前のViewControllerが表示されるのですが、これがiPhone6sだと **ドラッグからの`Interactive Transitions`が高頻度で動作しない** という不具合です。

![demo.gif](https://qiita-image-store.s3.amazonaws.com/0/16700/6466198b-9637-99a6-4ec0-08be50268daf.gif "demo.gif")
*[SloppySwiper](https://github.com/fastred/SloppySwiper)のREADME.mdから引用*


調査したところ問題の箇所は、

```objectivec:SSWDirectionalPanGestureRecognizer.m
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
	// 省略	

    CGPoint velocity = [self velocityInView:self.view];

	// 省略
}
```
*https://github.com/fastred/SloppySwiper/blob/master/Classes/SSWDirectionalPanGestureRecognizer.m#L22*

`UIPanGestureRecognizer`をサブクラス化してる`SSWDirectionalPanGestureRecognizer`で`-touchesMoved:withEvent:`内で使用してる`-velocityInView:`がCGPointZeroを返すことが原因でした。

# Force Touchを持つデバイスでは`-touchesMoved:withEvent:`の呼び出しタイミングが異なる 
iPhone4sとiPhone6sで画面をタップする(指を動かさない)とそれぞれ以下が呼びだされます。

### iPhone4s
	-touchesBegan:withEvent:


### iPhone6s
	-touchesBegan:withEvent:
	-touchesMoved:withEvent:

iPhonw6sだと指を動かしていないのに`-touchesMoved:withEvent:`が呼ばれています。そこで今回の問題の箇所である`-velocityInView:`は指を動かしていないためCGPointZeroが返ってくるというわけです。その後iPhone6sでも指を動かせば正常に`-velocityInView:`が値を返してくれました。

# 結論: Force TouchのZ軸方向への移動は`-touchesMoved:withEvent:`を呼びだす

iPhone6sで何が起きているかを調べてみたところ、このx,y軸方向への移動量ゼロの`-touchesMoved:withEvent:`で変化が起きていたのは[- [UITouch force]](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITouch_Class/#//apple_ref/occ/instp/UITouch/force)の値でした。
forceはiPhone6s(6s+)で追加されたForce Touch(3D Touch)の強く押し込んでいるときの圧力量です。

結論としてはForce Touch(3D Touch)搭載の機種ではx, y軸方向への移動以外にz軸方向への移動でも`-touchesMoved:withEvent:`が呼び出されるということです。たしかに移動という観点では正しい挙動だと納得できます。

稀に正常に動作していたのは初回の`-touchesMoved:withEvent:`でz軸方向への移動に加えてx, y軸方向への移動があった場合で、たまたま従来の処理方法でうまく動作していただけでした。

# そういえば

これに気づく数時間前にアプリ内でForce Touchを利用しやすくするためにGestureRecognizer化した[ForceTouchGestureRecognizer](https://github.com/yusuga/ForceTouchGestureRecognizer)を作っていました。

![preview.gif](https://qiita-image-store.s3.amazonaws.com/0/16700/a3e22e1d-8ae0-daaf-fe07-f2d8d72978f2.gif "preview.gif")


これの実装も[-touchesMoved:withEvent:](https://github.com/yusuga/ForceTouchGestureRecognizer/blob/master/Classes/ForceTouchGestureRecognizer/ForceTouchGestureRecognizer.m#L51)で検知していましたね・・・うっかり忘れてた・・・。

[ForceTouchGestureRecognizer](https://github.com/yusuga/ForceTouchGestureRecognizer)は`UIGestureRecognizer`のサブクラスなので、

```objectivec:ForceTouchGestureRecognizerExample/ViewController.m
[self.forceTapGestureRecognizer addTarget:self action:@selector(viewForceTapped:)];
[self.singleTapGestureRecognizer addTarget:self action:@selector(viewSingleTapped:)];
[self.doubleTapGestureRecognizer addTarget:self action:@selector(viewDoubleTapped:)];
[self.longPressGestureRecognizer addTarget:self action:@selector(viewlongPressed:)];
```
*https://github.com/yusuga/ForceTouchGestureRecognizer/blob/master/Example/ForceTouchGestureRecognizerExample/ForceTouchGestureRecognizerExample/ViewController.m#L30*

のように他の`UIGestureRecognizer`との共用も楽にできます。

上のgifはForce TouchとUITapGestureRecognizerの繰り返しですが、[Example](https://github.com/yusuga/ForceTouchGestureRecognizer)ではForce Touchに加えてシングルタップ/ダブルタップ/ロングプレスとの共用方法を実装しています。
アプリ内で利用可能なForce Touchを使いたい方はぜひお試しください！

# 追記: UIResponderでの対応

他にもデバッグを進めていたら`-[UIResponder touchesMoved:withEvent:]`も同様の挙動でした。
こちらは次の方法でx, y軸方向への移動があるかを判定して対応しました。

```objectivec:UIResponderSubclass
- (void)touchesMoved:(NSSet *)touches
           withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
	if (CGPointEqualToPoint([touch locationInView:self], [touch previousLocationInView:self])) {
		// x,y軸方向への移動がないForce Touchによるz軸方向への移動
		// ここで-[UITouch force]を利用してForce Touch時の挙動にも対応できる。
		return ;
	}

	// x, y軸方向への移動
}
```

また当初の問題だった[SloppySwiper](https://github.com/fastred/SloppySwiper)は元の実装上`-[UIPanGestureRecognizer velocityInView:]`を使っていたのでそのまま`-velocityInView:`を使い[解決 (Mergeされました)](https://github.com/fastred/SloppySwiper/commit/82a7d673a65751979d251bc8357fa09d8883464f)しましたが、`UIGestureRecognizer (またはサブクラス)`でもUIResponderで行なっている様な判定で対応できると思います。