Support directional remotes in your tvOS app

A hand on a video game controller

In addition to the Siri Remote, tvOS provides support for a variety of hardware and software remotes with touch or button interfaces. When your app uses standard frameworks — like UIKit and AVKit — tvOS will automatically handle remote interactions to provide a consistent and familiar experience. If you have an app that includes custom interface elements or media players, you can still easily support navigation and content-based features for hardware and software remotes with both touch and button interfaces. Here’s how.

Explore app navigation

On Apple TV, people use a remote or game controller to navigate through interface elements like content posters, apps, or buttons, highlighting each item as they come to it. The highlighted item is said to be focused or in focus. The focus engine in UIKit controls focus and movement on tvOS, listens for incoming focus-movement events triggered from a remote or game controller, and notifies your app so you can provide the appropriate experience. All standard gestures in tvOS work with directional remotes by default, and you can also provide support in your app for custom gestures with classes like UITapGestureRecognizer.

About Focus Interactions for Apple TV

Adapt custom gestures for directional remotes If you support custom gestures that control special actions within your app’s interface, you can add UITapGestureRecognizer to map to an explicit directional event.

// Gesture to handle directional pad down
let customGesture = UITapGestureRecognizer(target: self, action: #selector(specialAction))
customGesture.allowedPressTypes = [.downArrow]
view.addGestureRecognizer(customGesture)

UITapGestureRecognizer

Learn more about detecting gestures and button presses

Explore the App Programming Guide for tvOS

Support remotes with dedicated channel and guide buttons Some Apple TV-compatible remotes offer dedicated guide buttons and up and down arrows to control channel changes and electronic programming guide paging functionality. If your app includes channels or a programming guide, you can support these interactions on remotes with dedicated buttons through the TV Services framework.

Providing Channel Navigation

Support playback interactions in a custom video player

If your app includes video content, you can use AVPlayerViewController to provide the best experience across all remote types. If you’ve created a custom video player, however, you can use UITapGestureRecognizer map specific remote buttons to playback interactions as well as MPRemoteCommandCenter to handle media player events.

MPRemoteCommandCenter

Becoming a Now Playable App

Remote Command Center Events

Play and pause When someone plays content in your app, pressing select, play, pause or play/pause should update the playback state and display the player transport bar. You’ll need to use UITapGestureRecognizer and MPRemoteCommandCenter to handle these interactions in your custom player.

// Gesture to handle play pause state
let playPauseGesture = UITapGestureRecognizer(target: self, action: #selector(togglePlaybackState)
playPauseGesture.allowedPressTypes = [.playPause, .select]
view.addGestureRecognizer(playPauseGesture)

// MPRemoteCommands to handle play pause state
let remoteCommandCenter = MPRemoteCommandCenter.shared()
remoteCommandCenter.playCommand.addTarget(self, action: #selector(playHandler))
remoteCommandCenter.pauseCommand.addTarget(self, action: #selector(pauseHandler))

Skip forward and skip backward When using a directional remote, people can skip through video in the appropriate direction by pressing left or right on the directional pad. Additionally, certain remotes have dedicated skip forward and skip backward buttons that can map to this functionality. To ensure someone can navigate forward and backward through your content, use UITapGestureRecognizer and MPRemoteCommandCenter to create the correct gestures.

// Gesture to handle directional pad left
let dPadLeftGesture = UITapGestureRecognizer(target: self, action: #selector(dPadLeftHandler))
dPadLeftGesture.allowedPressTypes = [NSNumber(value: UIPress.PressType.leftArrow.rawValue)]
view.addGestureRecognizer(dPadLeftGesture)

// Gesture to handle directional pad right
let dPadRightGesture = UITapGestureRecognizer(target: self, action: #selector(dPadRightHandler))
dPadRightGesture.allowedPressTypes = [NSNumber(value: UIPress.PressType.rightArrow.rawValue)]
view.addGestureRecognizer(dPadRightGesture)

// MPRemoteCommands to handle skip forward and skip backward
let remoteCommandCenter = MPRemoteCommandCenter.shared()
remoteCommandCenter.skipForwardCommand.addTarget(self, action: #selector(skipForwardHandler))
remoteCommandCenter.skipBackwardCommand.addTarget(self, action: #selector(skipBackwardHandler))

Fast forward and rewind People can fast forward and rewind through content in different ways depending on the remote they use. Some remotes offer dedicated fast forward and rewind buttons, while others achieve this with a long press on the left or right directional pad buttons.


Note: On tvOS, we recommend using the following UI labels to pair with the corresponding supported player rates.

UI Labels to tvOS Playback Rates 1x = 8.0 2x = 24.0 3x = 48.0 4x = 96.0


Dedicated fast forward and rewind buttons For remotes with dedicated fast forward and rewind buttons, a short press will initiate a continuous seek forward or backward. Each subsequent press of that button will increase the seek rate. Once someone passes the last seek rate, the player returns to its normal playback rate. If the opposite button is pressed while seeking, it decreases the seek rate; each subsequent press will do the same until the player returns to its normal playback rate.

You can support these controls in your app with changePlaybackRateCommand in MPRemoteCommandCenter. You’ll also want to make sure to update your player rate with the playbackRate property of the MPChangePlaybackRateCommandEvent.

If someone holds a dedicated fast forward or rewind button, MPRemoteCommandCenter will fire a seekForwardCommand or seekBackwardCommand. Your registered target will receive MPSeekCommandEventType.beginSeeking, and your app should set the player to the desired seek rate. Once someone releases the button, your app receives a new command event with the case MPSeekCommandEventType.endSeeking, telling it to return the player rate to normal.

Directional pad fast forward and rewind If a person long presses the left or right directional pad buttons, your app should seek forward or backward respectively and update the seek rate. While seeking, a person can use a short press on the left or right directional pad buttons to update the seek rate as described in the previous section.

Adopt fast forward and rewind in your custom video player To incorporate these gestures in your app for all remotes, you’ll want to use MPRemoteCommandCenter, UITapGestureRecognizer and UILongPressGestureRecognizer.

// MPRemoteCommands to handle dedicated fast forward and rewind buttons
let remoteCommandCenter = MPRemoteCommandCenter.shared()

// MPRemoteCommands to handle dedicated fast forward and rewind (short presses)
remoteCommandCenter.changePlaybackRateCommand.addTarget { [unowned self] event in
    guard let event = event as? MPChangePlaybackRateCommandEvent else { return .commandFailed }
    self.assetPlayer.player.rate = event.playbackRate
    return .success
}

// MPRemoteCommands to handle dedicated fast forward button (long press)
remoteCommandCenter.seekForwardCommand.addTarget { [unowned self] event in
    guard let event = event as? MPSeekCommandEvent else { return .commandFailed }
    if event.type == .beginSeeking {
        self.assetPlayer.player.rate = 24.0
    }
    if event.type == .endSeeking {
        self.assetPlayer.player.rate = 1.0
    }
    return .success
}

// MPRemoteCommands to handle dedicated rewind button (long press)
remoteCommandCenter.seekBackwardCommand.addTarget { [unowned self] event in
    guard let event = event as? MPSeekCommandEvent else { return .commandFailed }
    if event.type == .beginSeeking {
        self.assetPlayer.player.rate = -24.0
    }
    if event.type == .endSeeking {
        self.assetPlayer.player.rate = 1.0
    }
    return .success
}

        
// Gestures to handle fast forward and rewind from directional pad

// Gesture to handle dpad right (long press)
let dPadRightLong = UILongPressGestureRecognizer(target: self, action: #selector(dPadRightLongHandler))
dPadRightLong.allowedPressTypes = [NSNumber(value: UIPress.PressType.rightArrow.rawValue)]
view.addGestureRecognizer(dPadRightLong)

// Gesture to handle dpad left (long press)
let dPadLeftLong = UILongPressGestureRecognizer(target: self, action: #selector(dPadLeftLongHandler))
dPadLeftLong.allowedPressTypes = [NSNumber(value: UIPress.PressType.leftArrow.rawValue)]
view.addGestureRecognizer(dPadLeftLong)

// Gesture to handle dpad right (short press)
let dPadRightGesture = UITapGestureRecognizer(target: self, action: #selector(dPadRightHandler))
dPadRightGesture.allowedPressTypes = [NSNumber(value: UIPress.PressType.rightArrow.rawValue)]
view.addGestureRecognizer(dPadRightGesture)

// Gesture to handle dpad left (short press)
let dPadLeftGesture = UITapGestureRecognizer(target: self, action: #selector(dPadLeftHandler))
dPadLeftGesture.allowedPressTypes = [NSNumber(value: UIPress.PressType.leftArrow.rawValue)]
view.addGestureRecognizer(dPadLeftGesture)

Support additional interactions in a custom player

People expect up or down swipes or presses to provide a consistent experience during video playback, like showing the player controls and transport bar on a press or swipe up, or showing the information panel on a swipe or press down.

Transport Bar People should be able to see how much time remains on a piece of content. Swiping up or pressing up on a directional pad should show the player’s transport bar without stopping or pausing the player.

// Gesture to handle directional pad up
let upGesture = UITapGestureRecognizer(target: self, action: .upGestureRecognizer)
upGesture.allowedPressTypes = [.upArrow]
view.addGestureRecognizer(upGesture)

Information Panel People should be able to access subtitle and audio selections along with other options while viewing content. Swiping down or pressing down on a directional pad should show these options without stopping or pausing the player.

// Gesture to handle directional pad down
let downGesture = UITapGestureRecognizer(target: self, action: .downGestureRecognizer)
downGesture.allowedPressTypes = [.downArrow]
view.addGestureRecognizer(downGesture)

Putting Your Users In Control No matter which interaction method someone chooses to use with Apple TV, you can ensure they remain in control by offering a great experience in your app for all remotes. It’s easy when you use system frameworks like AVFoundation and AVKit and support custom gestures and media interactions with UITapGestureRecognizer and MPRemoteCommandCenter. And when you do, it lets people enjoy your content without worrying how to interact with your app.