NEXTSCAPE blog

株式会社ネクストスケープの社員による会社公式ブログです。ネスケラボでは、社員が日頃どのようなことに興味をもっているのか、仕事を通してどのような面白いことに取り組んでいるのかなど、会社や技術に関する情報をマイペースに紹介しています。

MENU

Apple TVのリモコン(Siri Remote)のTouch Surface上で触っている位置を知る方法

Apple TV のリモコン(Siri Remote)には Touch Surfaceがあります。
通常リモコン操作をプログラムで検出したい場合、UITapGestureRecognizer(タップジェスチャー)やUISwipeGestureRecognizer(スワイプジェスチャー)を使用するか、低レベルのイベントをハンドリングします。
これで基本的な操作は検出できるのですが、Touch Surface領域のどの位置を触れているかの検出できませんでしたので、方法を調べてみました。

 

まずそもそも、なぜ検出できないのかAppleに聞いてみました。

以下のような理由だそうです。

We do not expose the physical location of the user’s finger from the Siri Remote’s touch pad. Part of the reason we do this is to discourage “pointer-based” UI screens, such as mouse cursors and similar navigation methods.

AppleTVのUIはフォーカス移動で行うことを想定しており、マウスカーソルのポインタのようなもので操作するUIを作らせないために意図的にこのような仕様にしているそうです。
ですので、これから紹介する方法は上記に該当するようなUIを作るためにはオススメしません。

これを踏まえて、
このTouch Surface上のどの位置が触れられているのかを検出する方法を紹介します。

 

 

f:id:nextscape_blog:20210911005834p:plain



Touch Surfaceは上記図の①の分部です。この領域内で”右端を触れている”、”左端を触れている”といったことを検出したいわけです。

まずは、一般的にリモコンの操作を検出する方法について触れます。

 

タップ・プレスの検出 UITapGestureRecognizer

let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapped(_:)))
tapRecognizer.allowedPressTypes = [NSNumber(value: UIPressType.select.rawValue as Int)]
self.view.addGestureRecognizer(tapRecognizer)

 

allowedPressTypes プロパティに検出したい操作を指定します。
  指定可能な操作は以下の通りです。

 upArrow   上矢印ボタンが押された。 (上記図①の上側)
 downArrow  下矢印ボタンが押された。(上記図①の下側)
 leftArrow  左矢印ボタンが押された。(上記図①の左側)
 rightArrow   右矢印ボタンが押された。(上記図①の右側)
 select   選択ボタンが押された。(上記図①)
 menu   メニューボタンが押された。(上記図②)
 playPause   再生/一時停止ボタンが押された。(上記図④)


(menuとplayPauseはTouch Surface外なので今回は割愛します。)

ここで、

select はTouch Surfaceをカチッと押し込んだ操作(押して離したタイミング)がを検出し、

upArrow, downArrow, leftArrow, rightArrow はTouch Surfaceを軽く触れる(触れて離したタイミング)を検出します。
押して離したタイミングが検出されますが触れた(触れている)タイミングは検出されません。

スワイプ操作の検出 UISwipeGestureRecognizer

 

let swipeRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(self.tapped(_:)))
swipeRecognizer.direction = .left


direction プロパティでスワイプの方向を指定します。
指定可能な方向は以下です。

 up   上方向のスワイプ
 down   下方向のスワイプ
 left   左方向のスワイプ
 right   右方向のスワイプ


こちらでも、スワイプが行われた際には検出できますが、触れたらているだけでは検出できません。

低レベルのイベントハンドリング

 

// タッチが開始された時
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touch = touches.first
    let point = touch?.location(in: self.view) ?? CGPoint()
// point.x or .y } // タッチ位置が移動した時 override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { let touch = touches.first let point = touch?.location(in: self.view) ?? CGPoint() // point.x or .y }

これだと、イベントに渡されたUITouchのlocationを使用するとTouch Surface上の位置が取れそうに見えますが実際には取れません。
ここで取得できる位置はタッチ開始時に触れた位置を(x,y) = (0,0)とし、移動した時は開始時の位置に対してどちらにどれだけ移動したかが得られます。

つまり、リモコンのTouch Surface上の右端を触れても左端を触れても(0,0)しか検出できず、相対的な移動方向しかわかりません。


他の方法をいろいろ調べて
以下の方法でタッチサーフェス上の触れられている位置を検出できましたので紹介します。

 

GameController

これは、ゲームのコントローラー入力を検出するためのものですが、これを使用するとTouch Surface部分の触れている位置を検出することが可能です。
以下は簡単なサンプルです。

import GameController

class ViewController: UIViewController { private var siriRemote: GCController! override func viewDidLoad() { super.viewDidLoad() … self.initSiriRemote() … } private initSiriRemote() { for controller in GCController.controllers() { if self.siriRemote == nil { self.siriRemote = controller // リモコン上の位置を絶対座標で返すように指定 self.siriRemote.microGamepad?.reportsAbsoluteDpadValues = true // 値が変化したきのイベント設定 self.siriRemote.microGamepad?.dpad.valueChangedHandler = { [weak self] (DPad, xValue, yValue) in print(“タッチされている位置 = (\(xValue), \(yValue))”) } break } } } }

valueChangedHandlerプロパティに触れらている位置が変化した時のイベントハンドラを設定しますが
ポイントはreportsAbsoluteDpadValuesプロパティをtrueに設定しておくことです。
trueにすることで、リモコンのTouch Surface上の絶対座業で位置が通知されるようになります。 座標はTouch Surfaceの中心が( 0, 0)、最右上が( 1, 1)、最左下が(-1,-1) となります。