はじめに
この記事は 3D Touch が発表される前に執筆したものです。iOS 9 ならびに iPhone 6s の 3D Touch とは異なる内容になります。実際に実装するのであれば 3D Touch を採用することをおすすめします。
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 を足すと最大半径、引くと最小半径となるようです。ですが、この誤差値を具体的にどう活用すれば良いのか、リファレンスを読んでもよくわかりませんでした。とにかくこのような値が取れることは確かです。
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 な方は適宜読み替えてください。
// タッチダウン
[self addTarget:self action:@selector(touchDown:withEvent:) forControlEvents:UIControlEventTouchDown];
// タッチダウンからのボタン領域内のドラッグ中
[self addTarget:self action:@selector(touchDown:withEvent:) forControlEvents:UIControlEventTouchDragInside];
- (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 をかなり圧縮する必要があったため、デモとしても伝わりやすくなったかと思います。