{"id":129802,"date":"2021-05-10T12:00:49","date_gmt":"2021-05-10T12:00:49","guid":{"rendered":"https:\/\/developer.apple.com\/news\/?id=33cpm46r"},"modified":"2021-05-10T12:00:49","modified_gmt":"2021-05-10T12:00:49","slug":"support-directional-remotes-in-your-tvos-app","status":"publish","type":"post","link":"https:\/\/sickgaming.net\/blog\/2021\/05\/10\/support-directional-remotes-in-your-tvos-app\/","title":{"rendered":"Support directional remotes in your tvOS app"},"content":{"rendered":"<div class=\"inline-article-image\"><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2022\/11\/support-directional-remotes-in-your-tvos-app.jpg\" data-hires=\"false\" alt=\"A hand on a video game controller\"><\/div>\n<p>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 \u2014 like UIKit and AVKit \u2014 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\u2019s how.<\/p>\n<div class=\"inline-article-image\"><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2022\/11\/support-directional-remotes-in-your-tvos-app-1.jpg\" data-hires=\"false\" alt><\/div>\n<h3>Explore app navigation<\/h3>\n<p>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 <em>focused<\/em> or <em>in focus<\/em>. 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 <code>UITapGestureRecognizer<\/code>.<\/p>\n<p><a href=\"https:\/\/developer.apple.com\/documentation\/uikit\/focus-based_navigation\/about_focus_interactions_for_apple_tv\" class=\"icon icon-after icon-chevronright\">About focus interactions for Apple TV<\/a><\/p>\n<p><strong>Adapt custom gestures for directional remotes<\/strong><br \/>\nIf you support custom gestures that control special actions within your app\u2019s interface, you can add <code>UITapGestureRecognizer<\/code> to map to an explicit directional event.<\/p>\n<pre class=\"code-source\"><code>\n<span class=\"syntax-keyword\">let<\/span> customGesture <span class=\"syntax-operator\">=<\/span> <span class=\"syntax-type\">UITapGestureRecognizer<\/span>(target: <span class=\"syntax-keyword\">self<\/span>, action: <span class=\"syntax-keyword\">#selector<\/span>(specialAction))\ncustomGesture.allowedPressTypes <span class=\"syntax-operator\">=<\/span> [.downArrow]\nview.addGestureRecognizer(customGesture)<\/code><\/pre>\n<p><a href=\"https:\/\/developer.apple.com\/documentation\/uikit\/uitapgesturerecognizer\" class=\"icon icon-after icon-chevronright\">UITapGestureRecognizer<\/a><\/p>\n<p><a href=\"https:\/\/developer.apple.com\/library\/archive\/documentation\/General\/Conceptual\/AppleTV_PG\/DetectingButtonPressesandGestures.html#\/\/apple_ref\/doc\/uid\/TP40015241-CH16-SW1\" class=\"icon icon-after icon-chevronright\">Learn more about detecting gestures and button presses<\/a><\/p>\n<p><a href=\"https:\/\/developer.apple.com\/library\/archive\/documentation\/General\/Conceptual\/AppleTV_PG\/index.html#\/\/apple_ref\/doc\/uid\/TP40015241-CH12-SW1\" class=\"icon icon-after icon-chevronright\">Explore the App Programming Guide for tvOS<\/a><\/p>\n<p><strong>Support remotes with dedicated channel and guide buttons<\/strong><br \/>\nSome 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.<\/p>\n<p><a href=\"https:\/\/developer.apple.com\/documentation\/tvservices\/providing_channel_navigation\" class=\"icon icon-after icon-chevronright\">Providing Channel Navigation<\/a><\/p>\n<h3>Support playback interactions in a custom video player<\/h3>\n<p>If your app includes video content, you can use <code>AVPlayerViewController<\/code> to provide the best experience across all remote types. If you\u2019ve created a custom video player, however, you can use <code>UITapGestureRecognizer<\/code> map specific remote buttons to playback interactions as well as <code>MPRemoteCommandCenter<\/code> to handle media player events.<\/p>\n<p><a href=\"https:\/\/developer.apple.com\/documentation\/mediaplayer\/mpremotecommandcenter\" class=\"icon icon-after icon-chevronright\">MPRemoteCommandCenter<\/a><\/p>\n<p><a href=\"https:\/\/developer.apple.com\/documentation\/mediaplayer\/becoming_a_now_playable_app\" class=\"icon icon-after icon-chevronright\">Becoming a Now Playable App<\/a><\/p>\n<p><a href=\"https:\/\/developer.apple.com\/documentation\/mediaplayer\/remote_command_center_events\" class=\"icon icon-after icon-chevronright\">Remote Command Center Events<\/a><\/p>\n<p><strong>Play and pause<\/strong><br \/>\nWhen 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\u2019ll need to use <code>UITapGestureRecognizer<\/code> and <code>MPRemoteCommandCenter<\/code> to handle these interactions in your custom player.<\/p>\n<pre class=\"code-source\"><code>\n<span class=\"syntax-keyword\">let<\/span> playPauseGesture <span class=\"syntax-operator\">=<\/span> <span class=\"syntax-type\">UITapGestureRecognizer<\/span>(target: <span class=\"syntax-keyword\">self<\/span>, action: <span class=\"syntax-keyword\">#selector<\/span>(togglePlaybackState)\nplayPauseGesture.allowedPressTypes <span class=\"syntax-operator\">=<\/span> [.playPause, .select]\nview.addGestureRecognizer(playPauseGesture) <span class=\"syntax-keyword\">let<\/span> remoteCommandCenter <span class=\"syntax-operator\">=<\/span> <span class=\"syntax-type\">MPRemoteCommandCenter<\/span>.shared()\nremoteCommandCenter.playCommand.addTarget(<span class=\"syntax-keyword\">self<\/span>, action: <span class=\"syntax-keyword\">#selector<\/span>(playHandler))\nremoteCommandCenter.pauseCommand.addTarget(<span class=\"syntax-keyword\">self<\/span>, action: <span class=\"syntax-keyword\">#selector<\/span>(pauseHandler))<\/code><\/pre>\n<p><strong>Skip forward and skip backward<\/strong><br \/>\nWhen 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 <code>UITapGestureRecognizer<\/code> and <code>MPRemoteCommandCenter<\/code> to create the correct gestures.<\/p>\n<pre class=\"code-source\"><code>\n<span class=\"syntax-keyword\">let<\/span> dPadLeftGesture <span class=\"syntax-operator\">=<\/span> <span class=\"syntax-type\">UITapGestureRecognizer<\/span>(target: <span class=\"syntax-keyword\">self<\/span>, action: <span class=\"syntax-keyword\">#selector<\/span>(dPadLeftHandler))\ndPadLeftGesture.allowedPressTypes <span class=\"syntax-operator\">=<\/span> [<span class=\"syntax-type\">NSNumber<\/span>(value: <span class=\"syntax-type\">UIPress<\/span>.<span class=\"syntax-type\">PressType<\/span>.leftArrow.rawValue)]\nview.addGestureRecognizer(dPadLeftGesture) <span class=\"syntax-keyword\">let<\/span> dPadRightGesture <span class=\"syntax-operator\">=<\/span> <span class=\"syntax-type\">UITapGestureRecognizer<\/span>(target: <span class=\"syntax-keyword\">self<\/span>, action: <span class=\"syntax-keyword\">#selector<\/span>(dPadRightHandler))\ndPadRightGesture.allowedPressTypes <span class=\"syntax-operator\">=<\/span> [<span class=\"syntax-type\">NSNumber<\/span>(value: <span class=\"syntax-type\">UIPress<\/span>.<span class=\"syntax-type\">PressType<\/span>.rightArrow.rawValue)]\nview.addGestureRecognizer(dPadRightGesture) <span class=\"syntax-keyword\">let<\/span> remoteCommandCenter <span class=\"syntax-operator\">=<\/span> <span class=\"syntax-type\">MPRemoteCommandCenter<\/span>.shared()\nremoteCommandCenter.skipForwardCommand.addTarget(<span class=\"syntax-keyword\">self<\/span>, action: <span class=\"syntax-keyword\">#selector<\/span>(skipForwardHandler))\nremoteCommandCenter.skipBackwardCommand.addTarget(<span class=\"syntax-keyword\">self<\/span>, action: <span class=\"syntax-keyword\">#selector<\/span>(skipBackwardHandler))<\/code><\/pre>\n<p><strong>Fast forward and rewind<\/strong><br \/>\nPeople 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.<\/p>\n<hr>\n<p><em>Note: On tvOS, we recommend using the following UI labels to pair with the corresponding supported player rates.<\/em><\/p>\n<p><strong>UI Labels to tvOS Playback Rates<\/strong><br \/>\n<em>1x = 8.0<\/em><br \/>\n<em>2x = 24.0<\/em><br \/>\n<em>3x = 48.0<\/em><br \/>\n<em>4x = 96.0<\/em><\/p>\n<hr>\n<p><strong>Dedicated fast forward and rewind buttons<\/strong><br \/>\nFor 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. <\/p>\n<p>You can support these controls in your app with <code>changePlaybackRateCommand<\/code> in <code>MPRemoteCommandCenter<\/code>. You\u2019ll also want to make sure to update your player rate with the <code>playbackRate<\/code> property of the <code>MPChangePlaybackRateCommandEvent<\/code>.<\/p>\n<p>If someone holds a dedicated fast forward or rewind button, <code>MPRemoteCommandCenter<\/code> will fire a <code>seekForwardCommand<\/code> or <code>seekBackwardCommand<\/code>. Your registered target will receive <code>MPSeekCommandEventType.beginSeeking<\/code>, 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 <code>MPSeekCommandEventType.endSeeking<\/code>, telling it to return the player rate to normal.<\/p>\n<p><strong>Directional pad fast forward and rewind<\/strong><br \/>\nIf 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.<\/p>\n<p><strong>Adopt fast forward and rewind in your custom video player<\/strong><br \/>\nTo incorporate these gestures in your app for all remotes, you\u2019ll want to use <code>MPRemoteCommandCenter<\/code>, <code>UITapGestureRecognizer<\/code> and <code>UILongPressGestureRecognizer<\/code>.<\/p>\n<pre class=\"code-source\"><code>\/\/ MPRemoteCommands to handle dedicated fast forward and rewind buttons\nlet remoteCommandCenter = MPRemoteCommandCenter.shared() \/\/ MPRemoteCommands to handle dedicated fast forward and rewind (short presses)\nremoteCommandCenter.changePlaybackRateCommand.addTarget { [unowned self] event in guard let event = event as? MPChangePlaybackRateCommandEvent else { return .commandFailed } self.assetPlayer.player.rate = event.playbackRate return .success\n} \/\/ MPRemoteCommands to handle dedicated fast forward button (long press)\nremoteCommandCenter.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\n} \/\/ MPRemoteCommands to handle dedicated rewind button (long press)\nremoteCommandCenter.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\n} \/\/ Gestures to handle fast forward and rewind from directional pad \/\/ Gesture to handle dpad right (long press)\nlet dPadRightLong = UILongPressGestureRecognizer(target: self, action: #selector(dPadRightLongHandler))\ndPadRightLong.allowedPressTypes = [NSNumber(value: UIPress.PressType.rightArrow.rawValue)]\nview.addGestureRecognizer(dPadRightLong) \/\/ Gesture to handle dpad left (long press)\nlet dPadLeftLong = UILongPressGestureRecognizer(target: self, action: #selector(dPadLeftLongHandler))\ndPadLeftLong.allowedPressTypes = [NSNumber(value: UIPress.PressType.leftArrow.rawValue)]\nview.addGestureRecognizer(dPadLeftLong) \/\/ Gesture to handle dpad right (short press)\nlet dPadRightGesture = UITapGestureRecognizer(target: self, action: #selector(dPadRightHandler))\ndPadRightGesture.allowedPressTypes = [NSNumber(value: UIPress.PressType.rightArrow.rawValue)]\nview.addGestureRecognizer(dPadRightGesture) \/\/ Gesture to handle dpad left (short press)\nlet dPadLeftGesture = UITapGestureRecognizer(target: self, action: #selector(dPadLeftHandler))\ndPadLeftGesture.allowedPressTypes = [NSNumber(value: UIPress.PressType.leftArrow.rawValue)]\nview.addGestureRecognizer(dPadLeftGesture)<\/code><\/pre>\n<h3>Support additional interactions in a custom player<\/h3>\n<p>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.<\/p>\n<p><strong>Transport Bar<\/strong><br \/>\nPeople 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\u2019s transport bar without stopping or pausing the player.<\/p>\n<pre class=\"code-source\"><code>\n<span class=\"syntax-keyword\">let<\/span> upGesture <span class=\"syntax-operator\">=<\/span> <span class=\"syntax-type\">UITapGestureRecognizer<\/span>(target: <span class=\"syntax-keyword\">self<\/span>, action: .upGestureRecognizer)\nupGesture.allowedPressTypes <span class=\"syntax-operator\">=<\/span> [.upArrow]\nview.addGestureRecognizer(upGesture)<\/code><\/pre>\n<p><strong>Information Panel<\/strong><br \/>\nPeople 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.<\/p>\n<pre class=\"code-source\"><code>\n<span class=\"syntax-keyword\">let<\/span> downGesture <span class=\"syntax-operator\">=<\/span> <span class=\"syntax-type\">UITapGestureRecognizer<\/span>(target: <span class=\"syntax-keyword\">self<\/span>, action: .downGestureRecognizer)\ndownGesture.allowedPressTypes <span class=\"syntax-operator\">=<\/span> [.downArrow]\nview.addGestureRecognizer(downGesture)<\/code><\/pre>\n<p><strong>Putting Your Users In Control<\/strong><br \/>\nNo 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\u2019s easy when you use system frameworks like AVFoundation and AVKit and support custom gestures and media interactions with <code>UITapGestureRecognizer<\/code> and <code>MPRemoteCommandCenter<\/code>. And when you do, it lets people enjoy your content without worrying how to interact with your app.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 \u2014 like UIKit and AVKit \u2014 tvOS will automatically handle remote interactions to provide a consistent and familiar experience. If you have an app that includes custom [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":129803,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[55],"tags":[],"class_list":["post-129802","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-apple-developer-news"],"_links":{"self":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/posts\/129802","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/comments?post=129802"}],"version-history":[{"count":0,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/posts\/129802\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/media\/129803"}],"wp:attachment":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/media?parent=129802"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/categories?post=129802"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/tags?post=129802"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}