LoginSignup
35
37

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-09-27

ForceTapGestureRecognizerはForceTouchGestureRecognizerに名前を変更しました。


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

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

demo.gif
SloppySwiperのREADME.mdから引用

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

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]の値でした。
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を作っていました。

preview.gif

これの実装も-touchesMoved:withEvent:で検知していましたね・・・うっかり忘れてた・・・。

ForceTouchGestureRecognizerUIGestureRecognizerのサブクラスなので、

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ではForce Touchに加えてシングルタップ/ダブルタップ/ロングプレスとの共用方法を実装しています。
アプリ内で利用可能なForce Touchを使いたい方はぜひお試しください!

追記: UIResponderでの対応

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

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は元の実装上-[UIPanGestureRecognizer velocityInView:]を使っていたのでそのまま-velocityInView:を使い解決 (Mergeされました)しましたが、UIGestureRecognizer (またはサブクラス)でもUIResponderで行なっている様な判定で対応できると思います。

35
37
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
37