Focus Interaction on tvOS

Session 215 WWDC 2016

The focus interaction model is a critical component of an engaging and intuitive experience on tvOS. Learn more about how it works, some additions and changes to the API, as well as tips and tricks for making your tvOS app even better.

[ Music ]

[ Applause ]

Good afternoon.

Hi. My name is Matthew Ricketson.

I'm a software engineer working on UIKit, and today we're going to talk about Focus Interaction on tvOS.

Last year we introduced tvOS, a great new platform for building apps on Apple TV.

And whether you're building a brand new app for Apple TV or looking to port an existing app over from another platform, it's immediately obvious that Apple TV is unlike other Apple devices.

There's no touchscreen on a TV or a mouse because these forms of user input just don't make sense in a living room environment.

Instead, we use this, a remote, like the Siri Remote, in order to control Apple TV from a distance.

The Siri Remote and Apple TV have been designed together to provide the best navigation experience.

But Apple TV also supports many other input devices, like game controllers, Bluetooth keyboards, and the new remote app.

And sometimes users might feel more comfortable using one of these other input devices in order to control their Apple TV, and tvOS helps you support all of these through a user interaction model that we call Focus.

With Focus interaction, users indirectly control the UI through a single focused user interface element and they can move focus around the screen using any input device.

Users will expect that your apps are optimized toward great with Focus interaction.

And to help with that, UIKit provides built-in focus behavior for all of its standard controls; however, you may also want to customize the focus behavior in your apps in order to take them to the next level and provide an even better user experience.

And for that, UIKit exposes a Focus API.

Now today's talk is about that Focus API and we split it into two parts.

First, we're going to dive into what's new in the Focus API this year in tvOS 10.

And then in the second part of the talk, we're going to go into an extended case study on how to build a custom, fully focusable control completely from scratch.

This is a great demo that will show you how to get started building unique interaction experiences in your tvOS apps.

Now a quick note before we get started.

We're going to assume that you have at least some familiarity with the Focus API and the basic concepts of how Focus works on tvOS.

But even if you're new to the platform, don't worry.

You can still generally follow along, and at the end of the talk today, we'll call out some useful resources to help you get started with building apps for Apple TV.

So what's new in tvOS 10 and to start, we've added some new APIs to enhance the preferred focus system.

Now as a quick review, when we talk about preferred focus, we mean the process of determining where Focus should show up after a Focus update.

Whenever Focus updates, for example when your app first launches, the Focus engine needs to know where focus should be directed to.

It does this by asking your app where it prefers Focus gets directed.

Let's look at a specific example.

When your app first launches, the Focus engine is going to try to figure out where Focus should show up by default, and it's going to start by looking at the focus environment that we're updating Focus to.

And when our app first launches, that's the window.

In this case, the Focus engine will ask the window what's its preferred Focus view is.

The window delegates this responsibility to its root view controller, which could also return some other view in the hierarchy as its preferred Focus view.

The Focus engine will then ask that view what its preferred Focus view is and it will continue following down this chain until it reaches the end, which is the last focusable view that it found as the new Focus view on screen.

Leveraging the Preferred Focus System is important to provide a good user experience in complex apps, especially.

So that focus is directed to the correct parts of the screen at the appropriate times.

In tvOS 10, we're providing a new property that allows your apps to easily express complex Focus preferences and the name of this property is appropriately called Preferred Focus Environments declared in the UIFocusEnvironment protocol and it returns an array of objects that also conform to the UIFocusEnvironment protocol.

And this property has two primary benefits.

First, by returning objects that conform to UIFocusEnvironment, you can specify that focus be directed to any environment on screen, not just views but also view controllers directly.

The second benefit of this property is that it returns an array of these focus environments, which allows you to specify an ordered list of preferences and this enables complex, multipart-preferences to be expressed much more concisely.

And there's a specific pattern where we can see this happening that Preferred Focus is commonly used in which is custom container view controllers.

Let's look at an example of that.

Say we have an app with a structure that looks like this.

We have a container view controller with two child view controllers.

In this example, we want our container to prefer that focus show up in the left child view controller first, but if that's not possible, focus should be directed to its right child view controller.

And we can express this logic very concisely using the new preferredFocusEnvironments property.

Here, we override preferredFocusEnvironments to return an array of both children with the left child as the first item in the array.

This is great because we don't have to check ourselves whether the left child view controller actually contains anything focusable.

We don't have to know anything about the contents of that controller.

We just tell the Focus engine what our preferences are and it takes care of the hard work for us.

So if our left child view controller in this example happened to not contain anything focusable, the Focus engine will automatically fall back to that right child view controller instead.

And in this example, maybe it does find something focusable.

So the preferredFocusEnvironments property helps us to express these preferences much more clearly and concisely, and if the first item doesn't produce anything focusable, we can easily fall back to the other environments in the list.

Next up, we have another enhancement to the Preferred Focus System and this is for more specific use case involving restoring Focus after view controller transitions.

So before we introduce the API, let's first look at an example to illustrate what exactly we mean by focus restoration.

Say we have an app that looks like this.

It's a simple view controller with a menu of buttons.

And our root view controller has preferred that by default we want our first button to become focused.

It did this using the preferredFocusEnvironments property that we just looked at.

Now suppose the user moves to the last button in the list and selects that button to present a detail view controller.

In that case we're interested in is when this detail view controller gets dismissed.

After the dismissal, focus needs to be restored back to our root view controller.

Now the user would probably expect that focus gets restored back to the button that was last focused, before our presentation, which is the last button in our list.

However, if we ask our root view controller at this time what its preferred focus environments are, it would still direct us to that first default button.

And so instead, the Focus engine automatically remembers the last focus item for us so that after the transition is complete, it automatically restores focus to that last button that the user would expect.

This is behavior that existed in tvOS 9.

While this is almost always the behavior that both the user and often the developer expects, there are some cases where you might want to restore focus to some other part of your user interface.

Suppose for example that while our detail view is presented, our app's underlying data model changed so that the last button now represents a completely different action or data item that it did previously.

In that case the user might not expect focus be automatically restored back to that last item.

In that case, we want the Focus engine to use your view controller's preferred focus environments in order to determine where focus should be restored to.

And in tvOS 10, we're providing an API that allows you to do just that.

It's called restoresFocusAfterTransition.

It's a Boolean property defined on UIViewController and the way it works is very simple.

If set to true, the view controller will use the default behavior.

It'll tell the Focus engine to use its default behavior restoring focus to the last item that was focused before the transition occurred.

If set to false, however, then the Focus engine will instead consult your view controller's preferredFocusEnvironments giving your app a chance to control where focus gets directed to on screen.

The property is set to true by default to maintain that previous default behavior and it affects view controller presentation, like we just talked about, but other common built-in view controller transitions as well such as navigation pops and moving focus back and forth between your view controller and the tab bar.

We recommend that when using this property, set it always be true or always be false so that focus updates within your view controllers in a consistent and predictable way.

We really hope that this property will help you solve some of those tricky edge cases that you found in your app.

To show you how to use both of these properties that we just talked about, I'd like to invite up my colleague Brandon for a quick demo.

[ Applause ]

Hi, everyone.

My name is Brandon and I'm here today to show you how to take advantage of some of the new Focus APIs available in tvOS 10.

So I've been working on a simple calendar app and it's coming along pretty well so far.

You can view your events for a given week and you can switch between weeks using the next and previous buttons.

Now when the app launches, we initially focus on the previous button when I think it would actually be a better experience if we focused on the user's next event.

So let's go modify our app to do that.

So to direct focus, we need to override the preferredFocusEnvironments property.

This returns an array of UIFocusEnvironment for the focus engine to search to search.

We'll start by adding our upcoming events for the rest of the week to our array.

If we have no more upcoming events, then we want focus to default to the next button.

So we'll add that to our array as well.

Now let's run this, see if it works.

All right, so this time when the app launched, we're focused on the super secret party I'm hosting after this session.

Now, I wonder what would happen if I canceled this party.

When an event is canceled, it becomes grayed out and unfocusable.

So I would expect focus would just go to our next event, which is a tvOS lab tomorrow morning.

But a good developer always tests their code.

So let's go try this out.

So I'm going to go to my AppDelegate and cancel my not-so-secret party and rerun this app.

All right, just as we expected, when the app launched, we're focused on the tvOS lab.

So because our first preferred focus environment was unfocusable, the Focus engine just moved to our next preferred focus environment.

We didn't have to modify any of our code to do that.

The Focus engine handled it for us.

Now if you want to view the details for an event, you can select it to bring up the Details page.

From here, you can swipe left to right to go to the next or previous event.

I received some bug reports from beta testers that when they exit this view, the focus isn't on the event that was just selected.

So in this case, we've selected the bash.

So if I menu out, I would expect that we would be focused on the bash.

Instead, we're still focused on the tvOS lab, which is our first available preferred focus environment.

So all we need to do is update our preferredFocusEnvironments code to return our selected event as our preferred focus environment.

So let's go back to our CalendarViewController, and I have the selectedEventView property here which keeps track of the selected event when we're in the Details page.

So if this property exists, I'll return it as my first preferred focus environment.

All right, let's run this, see if it works.

Okay. So I'll select the tvOS lab, move the selection to the bash, and then menu out, and we're still focused on the tvOS lab.

And I'm remembering now that Matt mentioned earlier in the session that by default, the Focus engine does not consult your preferredFocusEnvironments when restoring from a view controller.

So to change that behavior, we need to set the restoresFocusAfterTransition property to false.

So let's go do that in our [inaudible] coder method and rerun the app, see if it works now.

All right, we'll select our tvOS lab event again, move our selection to the bash, and menu out.

Awesome, so now we're focused on the bash, which is exactly what we wanted.

So by using the preferredFocusEnvironments and restoresFocusAfterTransition properties, we're able to provide a much better experience to our users.

Now I'd like to invite Matt back up on stage.

[ Applause ]

Thanks, Brandon.

That was a really great demo.

Using these two new properties, we hope that you can reduce the complexity of code in your apps and also provide a better user experience.

Now, next up I'm really excited to announce that we're bringing focus interaction support directly into SpriteKit.

Prior to tvOS 10, SpriteKit developers had to manually implement focus interaction in their SpriteKit games by manually keeping track of their own state and handling user events directly.

But now in tvOS 10, we're extending the Focus API, the same API you're used to in UIKit, to also support your SpriteKit games.

But first, what would this be useful for?

Well the first and most obvious use case for focus interaction in games is game menus.

Your users will expect that menus in games feel and behave similar to menus in other system apps.

The Focus Interaction model also makes sense for board games, where users navigate around a two-dimensional game board and select items on that game board, maybe move those around.

And in fact, Focus Interaction makes sense for any type of game where selection is a core gameplay mechanic.

And in any of this use cases, again users will expect navigation and selection to feel similar to the system focus behavior.

Otherwise, your app might feel weird or broken, unintuitive.

And so to help with that, we've extended the same Focus API that you're used to in UIKit to now also work with SpriteKit.

This is a shared API, and what this means is that SKNodes are now able to opt in to becoming focusable and taking advantage of all of the Focus engine's built-in capabilities.

UIKit and SpriteKit have been integrated to allow focus to move freely between your views and your nodes so you don't have to handle those use cases yourself.

So how exactly does this work?

First, let's review how Focus works for UIViews.

Prior to tvOS 10, UIViews were the only user interface elements that were capable of becoming focused, and they defined their focus ability using the canBecomeFocused property.

UIViews also conform to the UIFocusEnvironment protocol.

And this allows views to get focus update notifications and also control the behavior of focus within their subview hierarchy.

So to support SpriteKit focus, we're going to want to bring these same capabilities to SKNodes and we've made some changes to support that.

We're abstracting these same capabilities out into their own protocol that we call UIFocusItem.

This protocol defines both that canBecomeFocused property and conformance to the UIFocusEnvironment protocol.

Next, we refactored UIView and extended SKNode to conform to this new protocol.

And this allows both UIView and SKNode to share these exact same API while not requiring any changes in your existing UIKit code.

We've also made additions to other parts of the Focus API in order to take advantage of this new UIFocusItem protocol.

First, let's look at the UIFocusUpdateContext class, which provides contextual information during focus update notifications.

In order to support focus updates to SpriteKit nodes, we're introducing two new UIFocusItem based properties: previouslyFocusedItem and nextFocusedItem, and these tell you which items are involved in the update.

We've made a similar change to UIScreen, introducing a new focusedItem property that returns an object to type UIFocusItem and this allows your app to query what the current focus item is at any given time.

Now there's one more API change worth mentioning but it requires a little bit more background.

In tvOS 9, the UIFocusItem protocol defined the preferredFocusView property.

And prior to tvOS 10, this was how apps defined their focus preferences, like we were talking about earlier.

But you can probably see a problem with this, which is that it returns a type of UIView.

And if we're going to support SpriteKit, we're going to need to define focus preferences to our SKNodes.

And so this isn't going to work.

But luckily, earlier in the talk, we introduced this new preferredFocusEnvironments property, which does the same thing.

But because it returns an array of UIFocusEnvironment objects and now SKNode conforms to the UIFocusEnvironment protocol, we can now define focus preferences to views, view controller, SKNodes, any object that conforms to UIFocusEnvironment.

And this makes defining focus preferences in your environments much easier and concise.

And because preferredFocusEnvironments takes care of all the use cases for preferredFocusView does but also provides these other benefits that we've talked about, in tvOS 10 we're going to deprecate preferredFocusView.

So if you're currently using preferredFocusView in your applications, we recommend upgrading to preferredFocusEnvironments instead.

We think this will really help.

So that's Focus Interaction support in SpriteKit.

Focus API now supports generic focus items using this new UIFocusItem protocol.

Both UIView and SKNode conform to this protocol.

And for SpriteKit developers, you can now opt in to supporting focus to take advantage of all the Focus engine's capabilities.

For UIKit developers that don't use SpriteKit in their apps, there are no changes required.

The view-based API with the one exception of preferredFocusView is still available for your convenience.

We do encourage you, however, to start using this new API in your code to take advantage of more generic and future features.

For more details on exactly what you need to do in order opt in to focus support in your SpriteKit games, we recommend watching the What's New in SpriteKit session happening this week, where they go into more detail on exactly what you need to do to opt in.

So that's Focus Interaction support for SpriteKit.

And that concludes our overview of what's new in tvOS 10.

And so for the next part of our talk, we're going to go in-depth with a case study on how to build a fully custom, focusable control.

And for our example today, we're going to use a five-star rating control.

Now we've already done some work and we've built a first draft of this control and I'd like to show that to you.

We just used the normal UIButton class and presented the rating as the title of the button.

If you select the button, it'll bump up the rating, and if you keep selecting the button, it'll cycle through all the available ratings.

Now this is okay.

It gives us the functionality that we need, but we really want to take our app to the next level.

And this control, it's good but it's not very fun.

It's not very interactive.

And so we thought long and hard about this and we came up with a new design and this is what that looks like.

That's a lot better.

Instead of just saying one-star or two-star, we now visually indicate our rating using actual stars.

When you select the control, you go into an editing mode, where you can use the Focus system to change our rating between the stars.

And when we're done, we just press Select again and we exit editing mode.

This is a great example of a control that is optimized for focus interaction and we're going to walk you through building this control completely from scratch using the Focus API.

And for that, I'm going to hand things back over to Brandon.

[ Applause ]

All right, thanks, Matt.

So I have a project here which contains our simple button control, but it doesn't look as nice as the one Matt just showed us.

So let's just delete that.

Let's go ahead and drag in a view to represent our new control.

We'll set up some constraints on it quickly, so 100 points from the top, a width of 650, a height of 150, and we'll center it horizontally in our container.

Great. Now I've already created a rating control class to represent this control, so let's assign that as a views class.

And let's go check out this rating control class.

We chose a subclass from UIControl because it provides a lot of functionality to us for free.

For example, it determines if the control can become focused or not based on its enabled state; however, it doesn't have a focus/unfocused appearance.

We have to provide that ourselves.

So let's go do that.

We'll start by adding a corner radius and a background color and then we'll override didUpdateFocus in context with coordinator to provide both a focus and unfocused appearance.

So if the next focus item is our control, then we'll configure the focus appearance, which just consists of scaling the view, adding a shadow, and changing the background color.

If the previously focused item is our control, then we'll configure the unfocused view or appearance, which just rests all of our properties.

Now before I run this, I also want to add motion effects to our control.

Motion effects makes your app feel more responsive and helps the user find the focus item on screen.

So to do that, I'll create a motionEffectGroup to add horizontal and vertical motion when I move my finger around the touch surface.

Now I need to go back, and when our control becomes focused, I will add the motion effect, and when it becomes unfocused, I will remove the motion effect.

Now I make all these changes within addCoordinatedAnimations block.

This way, our property changes are in sync with our focus animations.

So let's see how this looks.

All right, so what we've done so far is create a very basic control which has both a focus and unfocused appearance and has motion effects.

So it moves when I move my finger on the touch surface.

But this is supposed to be a five-star rating control and we don't have any stars yet.

So let's go add those.

So first we need to add some properties to store our control state.

And then in our awakeFromNib function, we'll add a horizontal stackView and add five stars to that.

I've already created the star classes to save some time, but if you're following along, I configured their focus appearance using the same process that we just saw.

Now when the user selects the control, I want them to be able to edit the current rating.

So I'll add a selectGestureRecognizer to our control.

And when that's triggered, I'll toggle the editing state and request a focus update.

When a focus update happens, it consults our preferredFocusEnvironments.

So we'll override that property.

And if we're editing, we'll return our starViews as our preferredFocusEnvironments.

Otherwise, we'll just return our super implementation.

Let's run this, see how it looks.

Okay, so this time when our app launched, we have some stars in our control.

And when I select the control, focus moves to the first star within the control; however, I'm not actually able to move focus to the other stars.

This is because there's some space between the stars.

So when we try to move focus to the right, we actually end up focusing on our control, which just directs focus back to our first star using preferredFocusEnvironments.

So to fix this, we want to make sure our control is not focusable when we're editing.

So let's override canBecomeFocused and we'll return false if our control is currently editing.

So let's run this, see if that fixes things.

All right, that's much better.

So now I'll able to move focus between the stars.

But it's difficult to tell what the current rating is because we don't provide any visual feedback.

So to do that, we need to update the current rating when our focus changes.

So let's go back to our didUpdateFocus function, and at the bottom, if we're editing, we'll set the current rating based on the index of the currently focused starView.

And let's go back up to our current rating property and we'll tie that directly to the isSelected property on the starViews.

And this property just sets the background color on the stars.

So let's see how that looks.

Okay, so now when I select the control, it's easy to tell that the first star is selected.

And when I move focus, all the star select remain selected.

So it's easy to tell what the current rating is.

So this is pretty good but it's not great yet.

Right now I'm able to move focus outside the control, and I don't want to be able to do that when I'm editing the current rating.

Also, when I select the control, we always reset the current rating back to one star.

I think it would be better if we kept the current rating and just focused on the last selected star.

So let's go make those changes.

So we'll start by overriding the shouldUpdateFocus to restrict focus movement.

So if we're editing and the next focus item is not one of our star views, then we'll return false to say no, focus can't move.

Also, in our preferredFocusEnvironments code, we want to return our last selected star if we're editing and the current rating is greater than zero.

So this way we don't reset the current rating every time.

Let's run this one more time, see how it looks.

Okay, now I'm no longer able to move focus outside of the control.

And when I select a control, the current rating stays the same and we just focus on the last selected star.

So that completes our custom control.

By utilizing the Focus APIs, we were able to create a pretty complex yet very useful control.

Now I'd like to invite Matt back up on stage.

[ Applause ]

Thanks, Brandon.

I rate that demo with five stars.

Okay, we chose that demo because it was a great example of using many different parts of the Focus API in order to create a rich focus interaction experience.

And there are just a couple parts of the implementation that Brandon showed us that I want to highlight again because they're important for really creating a great user experience.

The first tip we have is to subclass UIControl.

UIControl provides a lot of basic functionality for free, for instance, as Brandon said, defining the focusability of your controls based on its IsEnabled property.

So when you're building custom controls, you should also subclass from UIControl.

We also used the focus animation coordinator in order to define our focus-related animation changes when changing our focus state.

This is also really important for making sure that our focus animations stay in sync with other focus animations happening in other parts of the user interface.

And we do this to make sure that focus naturally flows around the screen when the user is moving between different controls.

So when you're defining animations for your focus appearance, please use the focus animation coordinator.

And the last tip is to use motion effects.

Brandon showed us how to use motion effects to create a little bit of movement when you place your thumb on the Siri Remote and do a little bit of movement but not enough to actually move focus.

You can use the UIMotionEffect's API to implement this behavior very easily.

This is important because it allows your users to get this direct connection to your app so that they get immediate feedback as soon as they start using the Siri Remote.

It also helps the user locate focus on the screen so that they can sit down in front of their TV, wiggle the remote around, and immediately the focus element pops out.

Before we wrap us, just a few notes on testing.

The first is to point out that not all input devices have the same capabilities.

And this is definitely important when it comes to testing your custom controls.

Now the good news is that if you use the Focus API to build your custom controls, like we did in this demo, then your control should work with any of the input devices that we talked about earlier: Game controllers, Bluetooth keyboards, the Siri Remote, all of those other ones.

But not all these input devices have the same capabilities so that if your custom control relies on some custom event handling or gesture recognition, for example, let's say for example you're using the Siri Remote which generates touch events from its touch surface and you're using that to drive some capability within your custom control, well if a user is using a Bluetooth keyboard, they can't generate those touch events.

And so you'll have to do some extra work to make sure that your control can behave correctly and be functional for all users, in all situations.

And so definitely test custom controls that use custom event handling with different input devices.

Try out a Bluetooth keyboard, pick up a game controller, very important to maintaining a good user experience.

And finally, please, please test your controls with accessibility.

The good news here is that the Focus engine again does some work for you.

For example, if the user wants a high-contrast user interface, the Focus engine will helpfully put an extra ring around the currently focused item to really make it pop out on the screen and make it easier to see.

But for other accessibility features, such as VoiceOver, you're going to have the do the same kinds of custom work that you're used to on iOS to make sure that your controls behave well on tvOS.

And so please test your controls with features like VoiceOver to make sure that they behavior really well in all situations.

So that's our talk today.

We introduced two new API properties, preferredFocusEnvironments and restoresFocusAfterTransition to help manage preferred focus in your app.

We also announced the new focus interaction support for SpriteKit and we think this will really help reduce the complexity of your SpriteKit games.

And finally, we took you through an in-depth case study on how to build a custom focusable control using our five star rating control.

Now before we leave today, we'd like to mention some great online resources to help you learn more.

Specifically, you should definitely check out the Apple TV Tech Talks from 2016.

These cover these are our videos available online.

They cover a wide array of topics that are good for people getting new to the platform but also good if you've had some experience building for tvOS already.

Specifically, there's one section, Focus-Driven Interfaces with UIKit that goes more in-depth into some of the more basic aspects of the Focus API, some of the basic concepts, an in-depth overview of some of the parts of the API that we didn't cover today.

It talks about some best practices and also tools and debugging support, which we have available to help you with building your apps.

You can find these talks here at this URL.

And for more information on today's session, you can go to this URL to get some helpful resources.

There are some other related sessions this week that you should check out.

If you're building games with SpriteKit, definitely go to the What's New in SpriteKit session, where we also talk about how to specifically opt in your SpriteKit games for focus support and then also check out the What's New in tvOS in Mastering UIKit on tvOS sessions to really take your UIKit apps to the next level.

And of course, have a great conference.

Thank you for coming today.

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US