App Development

Lessons in tvOS App Development

Lessons in tvOS App Development blog featured image-510x296

Let’s Get Focused

With the release of Apple’s tvOS, a large number of developers have jumped into creating new applications for this exciting new platform. Very wisely, Apple built its new platform on the basis of its popular iOS platform, allowing many developers to quickly make new tvOS applications. The tvOS platform has also added a few new paradigms that extend beyond features provided in iOS. One of the most important new systems to understand in tvOS is the focus engine. In today’s post, we’ll go over two different items in the focus system that you can use to enhance the user experience.

Focusing

In tvOS, the system uses the concept of a focused item to show the user what they currently have selected. Many of the stock UI controls that Apple provides have an enhanced focus state where the focused UI element will expand in size and subtly respond to the user’s touch on the remote’s touchpad. The system determines which item should be focused using the tvOS focus engine. The focus engine also determines which elements can be focusable, which direction the focus is moving, and where the final focal point should land. The focus engine interacts with the application through the UIFocusEnvironment protocol and developers can use this connection to enhance the focus of their application. If you are unfamiliar with the basics of the UIFocusEnvironment, you may find the Apple developer documents on the subject to be a helpful read before continuing.

Custom Focus States

Many of the stock UIKit elements provide a nice focus state effect when focused. This includes both table view cells and collection view cells. As you use these elements, though, you will see that while the background of the element zooms, any items that you have higher in the cell’s view stack don’t really change at all. In the following video, you can see this behavior.

Embedded content: https://www.youtube.com/embed/MIXaKvPQeyw?rel=0&controls=0&showinfo=0

You can see that the cell does not expand the gradient view and the label does not move with user interaction. This is less than ideal from a user experience perspective and doesn’t look very polished. We can fix that, however, by adding a few lines of code to respond to a collection view cell gaining focus.

The first step to improving the focus state is to track when the current item gains focus. This is done by implementing the didUpdateFocusInContext function from the UIFocusEnvironment protocol. This function is called any time your view is part of a focus change event. It will be called both when your view will gain focus and when your view will lose focus. You can access the UIFocusUpdateContext passed into this function to determine if the next view gaining context is your view or another. For example, the following code will call the enableFocusedState when the current cell gains focus and call disableFocusedState when it loses focus.

override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {


        coordinator.addCoordinatedAnimations({ () -> Void in
            if context.nextFocusedView === self {
                self.enableFocusedState()
            } else {
                self.disableFocusedState()
            }
        }, completion: nil)
    }

Once this override is in place, we can then add the support for interacting with the user when focused. In tvOS, the subtle movement effects when the user touches the remote can be achieved by using a UIInterpolatingMotionEffect that you attach to the specific view. These motion effects tie directly to subtle changes to the user’s finger position on the Apple TV’s trackpad. You can provide constraints to the motion effect to limit the amount of movement as well. The following code snippet shows how you can add an up/down and left/right tilt to the title label when a user touches the trackpad.

let horizontalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.x", type: .TiltAlongHorizontalAxis)
horizontalMotionEffect.minimumRelativeValue = -3
horizontalMotionEffect.maximumRelativeValue = 3
self.titleLabel.addMotionEffect(horizontalMotionEffect)


let verticalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.y", type: .TiltAlongVerticalAxis)
verticalMotionEffect.minimumRelativeValue = -3
verticalMotionEffect.maximumRelativeValue = 3
self.titleLabel.addMotionEffect(verticalMotionEffect)

In addition, you can apply a transform to the label to allow it to subtly grow along with the image view in the collection view cell.

self.titleLabel.transform = CGAffineTransformMakeScale(1.2, 1.2)

Making similar changes to the gradient view, you can now see we have a much more pleasant focus effect on the cell as seen in the following video. [iframe id=“https://www.youtube.com/embed/fCQz22-Ka2s?rel=0&controls=0&showinfo=0”]

Focus Guides

Once we have some exciting focus effects, we may run into a case where the direction of a focus doesn’t work the way we expect or the system can’t actually focus the item. For example, the following screenshot shows two buttons on the screen, one at the bottom left and one at the top right.

blog-post-image-1 lessons-in-tvOS-app-development EL

In this layout, the user can’t easily swipe from the bottom left button to the top right. In order to fix this issue, we will need to utilize UIFocusGuides. UIFocusGuides are a subclass of a UILayoutGuide that can be used to route focus to another place on the screen. So for example, if a user swipes up from the bottom left button, there is no focusable UI component that is directly above that button, so the focus won’t change. If however, we place a focus guide there and tell it to focus on the top right button, the user could then swipe up and the top right button would then become focused.

A similar issue would then occur once the top right button was in focus. In this case, a swipe down would not have a UI element to focus on. We would need to add an additional focus guide under the top button that would move the focus to the bottom left button. The following annotated image shows the focus guides as lines and arrows indicating where they move focus.

blog-post-image-2 lessons-in-tvOS-app-development EL

In this case, we create two focus guides where each is 1 point high and the full width of the screen. Each focus guide routes the focus to the other button. To add these guides we add them in code within viewDidLoad.

// Focus guide that will allow a swipe up from the bottom button to focus on the top button
 self.toTopRightFocusGuide = UIFocusGuide()
 self.view.addLayoutGuide(self.toTopRightFocusGuide)
 self.toTopRightFocusGuide.preferredFocusedView = topRightButton


// Focus guide that will allow a swipe down from the top button to focus on the bottom left 
// button
self.toBottomLeftFocusGuide = UIFocusGuide()
self.view.addLayoutGuide(self.toBottomLeftFocusGuide)
self.toBottomLeftFocusGuide.preferredFocusedView = bottomLeftButton


// Add constraints to the focus guides
let vertical = NSLayoutConstraint.constraintsWithVisualFormat("V:[toBottomFocusGuide(1)]-(0)-[toTopFocusGuide(1)]-(0)-[button]", options: [], metrics: nil, views: ["toBottomFocusGuide": self.toBottomLeftFocusGuide, "toTopFocusGuide": self.toTopRightFocusGuide,
            "button": self.bottomLeftButton])


let horizontal = NSLayoutConstraint.constraintsWithVisualFormat("|[focusGuide]|", options: [], metrics: nil, views: ["focusGuide": self.toTopRightFocusGuide])
let toBottomHorizontal = NSLayoutConstraint.constraintsWithVisualFormat("|[toBottomHorizontal]|", options: [], metrics: nil, views: ["toBottomHorizontal": self.toBottomLeftFocusGuide])
        self.view.addConstraints(vertical + horizontal + toBottomHorizontal)

You can add focus guides anywhere in your application to help improve the navigation experience for your users.

A full sample application demonstrating these focus environment features on our GitHub account.

Quickstart-Guide-to-Kotlin-Multiplatform

A Quick Start Guide to Kotlin Multiplatform

Kotlin Multiplatform, though still experimental, is a great up-and-coming solution...

Read the article