iPhone で Force Touch（感圧タッチ）を実装してみた

![Capture 2015-04-26 00.00.30.png](https://qiita-image-store.s3.amazonaws.com/0/3603/70462c75-1836-2353-62cf-58ce4b544374.png)

# はじめに

この記事は 3D Touch が発表される前に執筆したものです。iOS 9 ならびに iPhone 6s の 3D Touch とは異なる内容になります。実際に実装するのであれば 3D Touch を採用することをおすすめします。

[3D Touch 予習](http://qiita.com/items/e4fb2ba8d8a7ae96b446)

# Force Touch の時代？

感圧タッチ搭載の MacBook や Apple Watch の登場で、タッチ UI に圧力の概念が生まれ、そしていよいよタッチパネルの弱点である「触覚フィードバックの不足」を克服しつつあるようです。実際に新しい MacBook のトラックパッドに触れてみるとその完成度に驚きます。これは次期 iPhone にも期待せざるを得ません。

> アップル、次期 iPhone にも感圧タッチを採用？Apple Watchや新MacBookのForce Touch応用(WSJ報道)
> http://japanese.engadget.com/2015/03/11/iphone-apple-watch-macbook-force-touch-wsj/

このような記事は妄想の域を出ませんが、現実味がありそうです。 → 3D Touch が登場しました！

その一方で iOS 8には面白そうな機能がしれっと追加されていました。

> iOS8からUITouchにタッチ半径を測定するプロパティがある
> http://qiita.com/yimajo/items/ce6ae6c2159deb69a781

iOS 8からは画面にタッチしている指の接地領域（大雑把な半径）が取れるようになりました。これをうまく使えば、現状の iPhone であっても Force Touch のようなことは実現できそうです。さすがに触覚フィードバックまではハードウェア（Taptic Engine）が足りないので無理ですが。

# UITouch

キモとなるのは2つのプロパティです。

* majorRadius
* majorRadiusTolerance

majorRadius が指の接地領域を円と見立てた時の半径、majorRadiusTolerance がデバイスに依存する誤差（？）だそうです。単位は point です。
majorRadius に majorRadiusTolerance を足すと最大半径、引くと最小半径となるようです。ですが、この誤差値を具体的にどう活用すれば良いのか、リファレンスを読んでもよくわかりませんでした。とにかくこのような値が取れることは確かです。

```objc
CGFloat majorRadius = touch.majorRadius;
CGFloat tolerance = touch.majorRadiusTolerance;
CGFloat maxRadius = majorRadius + tolerance;
CGFloat minRadius = majorRadius - tolerance;
```

## majorRadius は段階的にしか取れない

実は落とし穴があって、この majorRadius、段階的な値しか取れません。UIControlEventTouchDragInside でログに出してみるとこのようになります。

```
00:34:30.323 majorRadius: 25.647583, majorRadiusTorelance: 6.411896
00:34:30.689 majorRadius: 38.471375, majorRadiusTorelance: 6.411896
00:34:30.705 majorRadius: 38.471375, majorRadiusTorelance: 6.411896
00:34:31.056 majorRadius: 38.471375, majorRadiusTorelance: 6.411896
00:34:31.072 majorRadius: 38.471375, majorRadiusTorelance: 6.411896
00:34:31.089 majorRadius: 38.471375, majorRadiusTorelance: 6.411896
00:34:31.106 majorRadius: 51.305344, majorRadiusTorelance: 6.411896
00:34:31.122 majorRadius: 51.305344, majorRadiusTorelance: 6.411896
00:34:31.139 majorRadius: 51.305344, majorRadiusTorelance: 6.411896
00:34:31.156 majorRadius: 51.305344, majorRadiusTorelance: 6.411896
00:34:31.422 majorRadius: 51.305344, majorRadiusTorelance: 6.411896
00:34:31.439 majorRadius: 64.129135, majorRadiusTorelance: 6.411896
00:34:31.456 majorRadius: 64.129135, majorRadiusTorelance: 6.411896
00:34:31.472 majorRadius: 64.129135, majorRadiusTorelance: 6.411896
00:34:31.489 majorRadius: 64.129135, majorRadiusTorelance: 6.411896
00:34:31.723 majorRadius: 64.129135, majorRadiusTorelance: 6.411896
00:34:31.739 majorRadius: 76.952927, majorRadiusTorelance: 6.411896
00:34:31.756 majorRadius: 76.952927, majorRadiusTorelance: 6.411896
00:34:31.773 majorRadius: 76.952927, majorRadiusTorelance: 6.411896
00:34:31.789 majorRadius: 76.952927, majorRadiusTorelance: 6.411896
00:34:31.806 majorRadius: 76.952927, majorRadiusTorelance: 6.411896
```

majorRadius はもっと連続的に取れるものだと思っていたのですが、これはもうどうしようもないので、「こういうものだ」と受け入れるしかありません。

# Force Touch 判定処理

新しい MacBook のトラックパッドには感圧センサーが内蔵されており、それによって細かな圧力検知を行っています。オレオレ Force Touch では majorRadius によって指の接地領域の大きさではかるアプローチで攻めてみます。

さて、UITouch から majorRadius が取れれば、あとは適当な閾値を設定して、その前後でアクションを分岐してやれば擬似的な Force Touch が実現できそうです。UIControlEventTouchDragInside に設定したアクションに判定文を仕込んでみます。

Swifter な方は適宜読み替えてください。

```objc
// タッチダウン
[self addTarget:self action:@selector(touchDown:withEvent:) forControlEvents:UIControlEventTouchDown];
// タッチダウンからのボタン領域内のドラッグ中
[self addTarget:self action:@selector(touchDown:withEvent:) forControlEvents:UIControlEventTouchDragInside];
```

```objc
- (void)touchDown:(UIControl*)sender withEvent:(UIEvent*)event
{
	UITouch *touch = [[event allTouches] anyObject];
	CGFloat majorRadius = touch.majorRadius;

	if (majorRadius >= 45.0) {
		// 強タッチ
	}
	else {
		// 通常タッチ
	}
}
```

ここでは閾値を45.0としましたが、これは私のiPhone 6と人差し指で検証して感覚で決め打ちしたに過ぎません。これが親指だと majorRadius が違ってくるはずなので、本来なら指の判定も入れたいところですが、残念ながら現状の UIKit の公開されている API では無理そうです。

# 応用例

これを UIButton に実装すると、例えば強く押し込んだらボタンのラベルを表示する、バルーンヘルプを表示する、といったことが可能になります。タッチ UI の可能性が広がりますね。

また、majorRadius を円の半径として、Material Design の波紋効果のような演出にも応用できます。アニメーション gif をかなり圧縮する必要があったため、デモとしても伝わりやすくなったかと思います。

![forcetouch.gif](https://qiita-image-store.s3.amazonaws.com/0/3603/401a6d53-5e22-7f39-55f7-5c5f93c988dc.gif)
*通常タッチと強タッチで挙動を区別できている*