David Cordero

Directional clicks on tvOS

Published on 10 Jul 2017

If you have tried to detect directional clicks on tvOS you might have notice that it is not that easy.

Checking the documentation of UITapRecognizers and UIPressType, you will find the following list of available press types:

@available(tvOS 9.0, *)
public enum UIPressType : Int {
  case upArrow
  case downArrow
  case leftArrow
  case rightArrow
  case select
  case menu
  case playPause
}

But as soon as you try to use them, you will notice that the types upArrow, downArrow, leftArrow or rightArrow are only triggered as result of a tap gesture. When you click on the touchpad of Siri Remote, the only gesture that is triggered is select, regardless of the position of your finger during the click.

In addition to that, in tvOS, there is no way to get the precise location of the finger in the digitizer from an instance of UITouch. In fact, in order to avoid people creating pointer-based applications, the coordinates of any gesture in Siri Remote always start from the center of the touchpad wherever you actually start the gesture from.

GameController SDK to the W̶O̶R̶K̶A̶R̶O̶U̶N̶D̶ RESCUE !!

Thanks to GameController SDK we can have a lower level of abstraction with the controllers engine. And, lucky for us… it does allow getting the absolute directional pad values from the controllers (in our case, from Siri Remote)

So, we could get the directional pad information…

import UIKit
import GameController

enum DPadState {
    case select
    case right
    case left
    case up
    case down
}

class ViewController: UIViewController {

    private var dPadState: DPadState = .select

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        setUpDirectionalPad()
    }

    // MARK: - Private

    private func setUpDirectionalPad() {
        guard let controller = GCController.controllers().first else { return }
        guard let micro = controller.microGamepad else { return }
        micro.reportsAbsoluteDpadValues = true
        micro.dpad.valueChangedHandler = {
            [weak self] (pad, x, y) in

            let threshold: Float = 0.7
            if y > threshold {
                self?.dPadState = .up
            }
            else if y < -threshold {
                self?.dPadState = .down
            }
            else {
                self?.dPadState = .select
            }       
        }
    }
}

… and depending on that, we can process the different actions whenever the gesture with type .select is triggered:

func pressesBegan(_ presses: Set<uipress>, with event: UIPressesEvent?) {

    for press in presses {
        switch press.type {
        case .select where dPadState == .up:
            print("⬆️")
        case .select where dPadState == .down:
            print("⬇️")
        case .select:
            print("🆗")
        default:
            super.pressesBegan(presses, with: event)
        }
    }
}

Show me the code:

I have created this simple project that detects directional clicks on Siri Remote with the method previously described.

Feel free to follow me on github, twitter or dcordero.me if you have any further question.