Focus Interaction in tvOS 11

Session 224 WWDC 2017

Focus interaction is the primary interaction model for UIKit-based apps on tvOS. Learn about new focus animation APIs, custom sounds, support for SceneKit and SpriteKit games, and new debugging tools for your development workflows. Gain insight into how to get the most from these new technologies in your apps.

Good morning.

[ Applause ]

Welcome to Focus Interaction in tvOS 11.

My name is Matt, I'm a software engineer working on UIKit and I'll be joined later today by my colleague Jon.

We have a lot to cover today, so let's get started.

First, we're going to showcase some of the new features and enhancements that we've made for Focus Interaction support in tvOS 11.

These cover a number of requests that we've received from you over the past year.

Next, Focus Interaction support is now in SceneKit and so we're going to cover everything that you need to know in order to get started.

And finally, we've built some great new focus debugging tools in tvOS 11 for helping you diagnose focus related issues in your app.

We think these new tools are going to save you a ton of time.

But before we start it's important that we review some of the basics of how the Focus API works into tvOS 11.

This information is going to be important throughout our session today.

From the perspective of the focus engine your app is app is made up of focus items and focus environments.

Focus items are the user interface elements that are capable of becoming focused.

They conform to the UIFocusItem item protocol.

Examples of focus items include UIViews in UIKit and nodes in SpriteKit.

Focus environments conform to the UIFocusEnvironment protocol.

Environments are allowed to influence the focus behavior in your application and they also get notified about focus updates.

All focus items are also focus environments, but these also cover other objects like view controllers.

Focus items and focus environments are arranged in a hierarchy.

In UIKit this hierarchy loosely follows the view and view controller hierarchies and in SpriteKit it follows the node hierarchy.

This hierarchy is important when it comes to getting notified about focus updates.

A focus update can happen in one of two ways.

First, a user can move focus geometrically, such as by swiping on the Siri remote or your app can trigger a programmatic focus update, such as by calling APIs like setNeedsFocusUpdate.

When a focus update happens, we notify every focus environment that contains both the previously focused item and the next focused item.

And this includes all common ancestor environments as well.

We notify environments in ascending order starting with the two items that are directly involved in the update.

Notifications are sent by calling the didUpdateFocus method, which is defined on the UIFocusEnvironment protocol.

And this leads us straight into our first topic of today, which is that in tvOS 11 we're providing a few helpful new ways to get notified about focus updates.

Let's jump back to that diagram.

Like I was just saying, we notify your app using this didUpdateFocus method defined on UIFocusEnvironment.

But sometimes there might be another object in your app not a focus environment that you don't want to be loosely coupled to the rest of your user interface code.

So, for this case we're introducing a new foundation notification type called UIFocusDidUpdate.

And you can observe this from anywhere in your app and it will get sent every time focus updates.

This notification includes all the same information that you're used to getting for a normal focus update, including a context that describes the update, as well as an animation coordinator for controlling visual feedback.

You can access both of these objects using two new user info keys that we're providing.

The UIFocusUpdateContext key and the UIFocusUpdate AnimiationCoordinator key.

We're also introducing another key notification type related to focus updates and that's called UIFocusMovementDidFail and this notification is sent whenever the user tries, but fails to move focus in a certain direction.

This will only be sent when the user is trying to move focus not when your app fails to perform some kind of programmatic focus update.

And this is really useful for cases where you might want to provide some kind of feedback to the user when they try to move focus in a direction that they're not allowed to.

Now a good example of this is on Apple TV where VoiceOver will provide a sound when a user tries to move focus in a direction that they're not allowed to.

And this is a really helpful way to provide some extra auditory feedback to visually impaired users letting them know that their action was appropriately received, but that it didn't have any effect.

So that's it for focus update notifications.

We're also providing a few new helpful protocol extensions on the UIFocusEnvironment and UIFocusItem protocols to help you write more succinct and safer code.

The first of these is a pretty easy one we're adding an isFocused property to the UIFocusItem protocol and this is just a simple way to check if any item is currently focused.

We've exposed this property on UIView since tvOS 9, but now that we're exposing it on the protocol UIFocusItem item directly you can use this with any type of focus item and that includes SpriteKit nodes.

We've also added a new contains method to the UIFocusEnvironment protocol.

This method allows you to easily check if one focus environment contains another focus environment, but without having to know the underlying types involved in the check.

Let's look at an example of this.

Suppose we want to check if an item that just got focused is contained by our current view controller.

We can accomplish this by simply calling the contains method on the view controller itself and passing in the next focus item directly.

This will work if that item is a view, it'll work if it's a SpriteKit node, it doesn't matter what the types are involved, you can just call contains and it'll just work.

And so, this will hopefully cut down on the amount of typecasting that you have to perform in your code.

So that's it for new protocol extensions.

For our next enhancements, I'd like to invite up my colleague Jon to talk to you about some new APIs for focus animations.

Good morning, last year we showed you the coordination API that you can use to keep your animations in sync with the system animations during a focus update.

And this is done by using the UIFocusAnimationCoordinator that is provided whenever a focus update occurs.

Today, I'd like to talk to you about some enhancements that we've made to this API in tvOS 11.

But before I get started let's take a more in-depth look at how animations work today.

Focus animations are a really important part of the tvOS user experience.

Not only do they show your users where focus has moved they help them feel more connected with your content.

But as with most things, timing is essential.

If the animations are too fast it creates a jarring effect for your users.

And if they're too slow well they're getting in the way.

So, UIKit has a lot of work put into it to manage these animations and their timing.

We do a lot of manipulations, such as duration changes, to keep the animations feeling fluid.

Quicker movements through multiple focus items will result in quicker animations.

And when we move slowly from one item to the next we slow down the animations to match.

When a user moves focus to an item offscreen we make sure to add an appropriate delay until that item is fully visible before we perform the focusing animations.

And you might have noticed that focusing animations and unfocusing animations have a different set of timing.

This is because focusing animations should be prominent and they should grab the user's attention, whereas unfocusing animations should be subtler and more in the background.

So, you can see here as focus moves between these items, the item becoming focused pops up very quickly and the item becoming unfocused slowly moves into the background.

In fact, it leaves a trail behind it as focus has moved from it to the next item.

So, when you call addCoordinatedAnimations to synchronize your animations we automatically determine whether these are focusing or unfocusing animations based on the focus environment hierarchy.

So, here's the same diagram Matt showed earlier for the didUpdateFocus notifications.

Whenever you call addCoordinatedAnimations from either the previously focused item or any of its direct ancestors up to but not including the common parent environment we consider these unfocusing animations and we coordinate them as such.

Conversely, if your animations are added from either the next focused item or any of its direct ancestors we consider these focusing animations and so they get that more prominent timing.

And this works really well.

But in tvOS 11 we're now allowing you to specifically target either the focusing or unfocusing animations.

And we're doing this by adding two new methods to the UIFocus AnimationCoordinator, addCoordinated FocusingAnimations and addCoordinated UnfocusingAnimations.

So, addCoordinated FocusingAnimations will function exactly the same as if you call addCoordinatedAnimations from within the next focused item or any of its direct ancestors.

And addCoordinated UnfocusingAnimations will function the same as addCoordinatedAnimations from within the previous focused item or its ancestors.

And so, the best way to show you how this works is with a demo.

So, let's dive right in.

All right, so I'm working a very simple timeline application that shows some entries from past dubdub events and their keynotes.

And each entry is represented by this cell that has a number in it and as we move focus the corresponding image above moves down to give a little bit of emphasis.

As I move through these you might notice that it feels a little bit choppy as we move quickly.

Well let's hop over and take a look at the code.

And you'll see that everything is done within the addCoordinatedAnimations block.

And this is a great use for our new API, what we have here is the focusing view and I've gotten a reference to this by using the nextFocusedIndexPath to grab the supplementary view it corresponds with and I'm simply doing a transform of 100 points down.

And then similarly for the unfocusing view the image that's going back up I get it by using the previouslyFocusedIndexPath and then transforming to the identity.

So instead of using the single addCoordinatedAnimations call which in fact makes everything coordinate alongside the focusing animations, we can now split this up into two explicit calls, one for the focusing animations for the focusing view and then one for the unfocusing animations for the unfocusing view.

So, now let's rerun this and take a look.

Now as we move from one item to the next you can see that the same trail as focus moves in the cells itself exists in the image views themselves and this creates a much nicer user experience for your application.

All right, so now let's hope back over to the slides.

And there's one other thing I'd like to talk about regarding animations.

In addition to the two new methods we're also providing an instance of the UIFocusAnimationContext and this is a new object in tvOS 11 that gives you some additional timing information about the animations occurring which should make advance animations even easier.

So, let's say we'd like to run an animation during the second half of the system focusing animations.

Well this is very easy to do, all we need to do is grab the duration off of the animation context, divide it by two, and then pass these values into the standard UIView animate method and we're done.

One important point to mention is the overrideInheritedDuration option that needs to get passed in because without this the nested view based animations are going to ignore the duration that you gave and instead use the outer duration.

But this doesn't just make you UIVIewAnimation easier it also provides better non-UIKit support.

So, if you're using SpriteKit which is fully supported by the focus system it allows you to create a more consistent feel for your animations using SK actions for example.

This is an example of using SK actions to fade the opacity to 50% using the exact same timing that the system UIKit focusing animations would.

All right, so the existing API works really well.

And if you don't have a need to explicitly target either focusing or unfocusing continue using the existing API because it does the right thing for you.

However, if you need to target your animations to focusing or unfocusing regardless of their association with the focus environment hierarchy use the new APIs and you'll be able to do that to great effect.

And lastly, the focus animation context is provided for some additional timing information which should make advanced animations even easier.

So, next I'd like to talk about focus sounds.

Where animations give a visual representation of the focus update focus sounds provide an auditory queue to the user that focus has moved.

And just like animations there's some subtlety to sound playback to create the best user experience possible.

UIKit performs a number of volume and panning modifications to every sound that's played.

So, faster focus movements result in a quieter sound and slower focus movements result in a louder sound, which nicely complements the same duration modifications we make to animations.

We also use the location of the focusing item onscreen to pan the volume either stereo left or stereo right.

So, if you swipe right across the screen you'll notice sound starts to pan to the right and as you swipe back left you'll notice sound pans to the left bouncing out when focus is centered horizontally.

Again, this helps create a much more immersive user experience.

But a lot of requested that we allow you to provide custom focus sounds or to opt out of the UIKit sounds and started in tvOS 11 I'm happy to announce that we're allowing you to do both.

So, to play custom sounds you simply need to register your sound with an identifier and then provide that identifier during a focus update that will play a focus sound.

This is only a user initiated focus movement.

And then once we have that identifier we'll play the sound it's associated with.

To register sounds you'll use a class method register soundFileURL for sound identifier which is exposed on the new UIFocusSystem, a new object in tvOS 11.

The sound registration is a global action, meaning that once you do it that custom sound is available throughout your entire application.

Because of this, you should only register a sound for a single identifier.

In fact, reregistering with the same sound identifier is an error.

But it's perfectly acceptable and reasonable to have multiple sounds in your application, just give each one of them a unique identifier.

Because it's global you also want to register early.

There's a nontrivial performance cost to setting up your custom sounds for playback and we want to make sure there's as much time between registration and user focus movement so that sounds can play seamlessly.

The registration method accepts all standard iOS file formats that are locally stored on your device.

So, when you actually need to play the sound you'll use the new optional method on the UIFocusEnvironment protocol.

So, we call this method soundIdentifier ForFocusUpdate.

In the same ascending order that the didUpdateFocus notifications are delivered.

And we start by calling this method on the next focused item.

If the next focused item gives us a no focus sound identifier we use that to control sound playback for this focus update and then we're done.

However, if we don't get a sound identifier back from the next focus item we look to its parent and we try again.

Now if the parent doesn't return anything we repeat this process and we go up to its parent and we repeat this over and over until we get to the root focus environment which if none of your focus environments have chosen to provide a custom sound then we simply use the default that UIKit would use on its own.

So, there are a number of options for what values you might return from sound identifier for focus update.

Of course, you're able to return any of the sounds that you had previously registered and doing so will play the sound that that identifier was registered with.

UIKit also provides two sound identifiers, default and none.

By providing the default sound identifier it explicitly signals to UIKit that you want to force the default sound that UIKit would play on its own to be played for this focus update.

Now keep in mind if none of your environments wish to control sound for this update then this is value we'll use anyway.

So, it's probably rare you'll need to use, this provide this for the cases that you do.

And last, the non-identifier can be used to entirely opt out of focus sounds for this focus update.

You're able to very easily conditionally modify which sound is played and using the same contains protocol extension that Matt showed earlier we're able to determine when focus moves through a particular environment and when it does modify the sound, otherwise deferred to pair it.

So, I here I have a particular environment, I'll call it sound enabled environment where when focus moves through this environment I want to play a sound that I registered with my custom sound identifier.

Again, it's very easy to do, take the next focused item and then just check whether or not that's contained within the sound enabled environment.

So, a couple quick points on opting out of focus sounds.

First, users expect a sound to be played when focus movement occurs.

Like I mentioned before, this is an auditory queue to them that focus has moved.

So, you should only be opting out of sounds if the user is explicitly expected to.

A great example of this might be an in-game menu where the user can turn off sounds for your entire application.

Also, if you were think about opting out of focus sounds and then playing your own when a focus update occurs, I highly encourage you to use our API instead because not only do you get the same volume and panning adjustments that we perform for our internal sounds which are probably very hard to replicate.

We also respect the user's device settings for playing these sounds.

So, Apple TV has a setting that allows you to turn off navigation sounds for the entire system and you don't want your application to be the only one playing sounds if the user says they don't want them.

All right, so to recap.

You should use good judgment when determining when and where and which sounds to play for focus update because your sound should enhance the user experience they should not detract from it.

A great example of this might be differentiating between elements onscreen.

We do this in UIKit based on the size of particular items.

So, very large focus items have a larger, deeper sound and smaller items have a smaller, higher pitched sound.

Again, this is all to enhance the user experience.

All right and with that I'd like to turn things back over to Matt who will talk about support for SceneKit.

Matt.

[ Applause ]

Thanks Jon.

All right, I'm excited to announce that in tvOS 11 we're now extending focus system support into the SceneKit framework.

So, how does this work?

Well if you recall back to tvOS 9 it used to be that UIView was the only type that was able to be focused.

In tvOS 10 we introduced this new UI focused item protocol and we extended focus interaction support into SpriteKit by adopting this protocol on both UIView and SKNode.

Well now in tvOS 11 we're adding SCNNode to the UIFocusItem family.

So, how does this work?

Just like SpriteKit you need to opt in in order to get focus support in your SceneKit apps.

This means that nodes are not focusable by default.

In order to make them focusable it couldn't be easier, all you have to do is set the new focus behavior property to focusable and that's it, it's that easy.

FocusBehavior is a new property on SCNNode and we've also made this property available on SKNode so now it's even easier to make your SpriteKit nodes focusable as well.

But I think it would be a lot more fun to just show you this in action with a quick demo.

All right, I've been building a tic-tac-toe app for tvOS and I really wanted my app to just have an extra bit of pop and so I decided to implement it with SceneKit.

And so, you can see my three-dimensional tic-tac-toe board with full perspective and I've already implement focus interaction support in my tic-tac-toe game.

So, I can move focus around using any type of input device to each of the tiles on my board.

And if I press Select I can drop an X or an O anywhere on the tile, on any tile.

So, implementing this couldn't have been easier, in addition to just setting up my SceneKit scene all I had to do was mark each of the nodes that represent the tiles on the board as focusable using that one line code I showed earlier.

And then provide custom animations using those same animation APIs that Jon was just talking about a moment ago.

And that's really all that we had to do.

Now we can fully interact with our SceneKit scene.

And the focus engine automatically takes care of everything else for us.

In fact, I added two UIKit buttons, UI buttons to the bottom of our SceneKit scene by just placing them on top of our SK view and the focus engine will automatically handle moving focus between our nodes and these buttons.

And it'll do the correct thing no matter whether I'm on the left or right side of the board hitting the correct button based on what's geometrically closer to our nodes.

And so, that's just a quick demo of SceneKit support for focus interaction.

[ Applause ]

There are just a few more things to keep in mind when implementing focus interaction support in your SceneKit apps and games.

First, SceneKit obviously operates in three-dimensional space, but focus interaction operates in two-dimensional space.

So how does this work?

Well when a user tries to move focus in a certain direction we're going to look for the next focusable SceneKit node based on its area as it's drawn onto the screen.

Second, now that we support focus interaction in SceneKit that means we're now also able to support your focusable SpriteKit scenes that are embedded within SceneKit scenes.

And in fact, we automatically support moving focus between UIKit, SpriteKit and SceneKit objects with no extra work required on your part.

So, you can build your user interface using whatever tools make the most sense for different areas of your application and focus interaction support will just work automatically.

And then finally, all the focus APIs that we define in UIKit using the UIFocusItem and UIFocusEnvironment protocols also work for SpriteKit and SceneKit.

And so, those custom sound and animation APIs like Jon was explaining earlier, those work for SceneKit as well and really helped to create a more immersive user experience.

To learn more about working with both SceneKit and SpriteKit we encourage you to check out some of these related sessions that we're putting on this week.

Now for our grand finale today I'd like to talk about something that definitely affects all of us and that's debugging.

I'm happy to say that in tvOS 11 we're introducing some great new focus debugging tools that we've built right into UIKit.

And first up is live focus update logging.

By including the UIFocusLoggingEnabled option in your Xcode schemes launch arguments you can turn on live focus update logging.

Now what's really helpful about this is that for each programmatic focus update that gets logged we're going to include a step-by-step outline of the preferred focus environment's search logic that the focus engine used to determine the next focus item.

And so, this is incredibly helpful for diagnosing issues where focus is not programmatically updating to the item that we would expect it to.

And because these are logs this can help you diagnose issues like focus not updating to the correct item on app launch and during transitions.

However, this tool does come with one warning which is that please only use this for debugging.

There is a nontrivial performance cost that comes with adding logging to your application so you never want this enabled for shipping applications.

So that's focus update logging.

The next tool that we're unveiling today is UIFocusDebugger which is a new class that we're exposing in tvOS 11.

UIFocusDebugger is a tool for diagnosing focus related issues at runtime in your app and this is really cool.

It's structured like a miniature command line tool that's built right into UIKit available for you to use.

To take advantage of it all you need to do is call any of its methods from LLDB while your app is paused.

Let's take a look at some of its capabilities.

The simplest one is that you can simply call status to quickly print out the currently focused item at any time.

And this is often step one in diagnosing some focus related issue, especially if you can't see where the currently focused item is.

Next, you can use the simulate focus update request from environment method to diagnose issues related to your app's preferred focus logic.

Calling this method is similar to calling the setNeedsFocusUpdate API and having that focus update execute on the environment that you pass in immediately.

But what's great about this is it outputs that same step-by-step preferred focus environment search logic that we also include in the live focus update logging.

But with this simulation method you can simulate any focus update anywhere in your application without having to actually trigger that focus update manually in your UI.

And so, this helps with quickly diagnosing different preferred focus environments issues in different places of your app.

Personally, my favorite tool in UIFocusDebugger is this next method, check focus ability for item.

And this is used to print out a list of potential issues that may cause an item to not be focusable.

We've all had that kind of issue where there is some item on the screen that we expect to be focusable, but for some reason we just can't focus on it and we don't know why.

And in fact, there are many different reasons why an item might not be focusable and they could be issues not just with that item itself, but with ancestors of that item that affect their children.

And so, this is where this new focusability for item method really shines.

It will automatically detect and diagnose all of these potential issues for you and print out a list of the issues that it detects and this allows you to save a ton of time.

Obviously, investigating all of these issues on your own would just be a huge waste of time.

And then finally, like any good command line tool you can always just ask UIFocusDebugger for help and this is going to print out a detailed list of instructions for each of the commands that are available and so don't worry about remembering all of these methods right now because the help command has you covered.

But the best way to show you all these new tools is with a quick demo.

All right, so I'm in the process of building a new tvOS application, but I've already run into some pretty serious focus related issues so let's see if we can figure out what's going on.

I'm going to build and run our application.

All right, so this is just a simple app that showcases some my favorite movies and the first thing you might notice is that nothing appears focused onscreen.

And in fact, if I swipe on the remote or if I try to move focus around the screen using the arrow keys nothing appears focused.

So, I'm at a loss here, don't really know what's going, let's see if UIFocusDebugger can help.

We're going to go back to our application, pause it, which will be LLDB and like I was saying earlier, a good thing to check is the status of the current focus system.

So, I'm going to type in UIFocusDebugger status and these calls work with both Objective-C and Swift as well.

And what status is telling me is that there is an item currently focused, it's one of those poster cells.

But obviously, if we take a look it doesn't appear focused and so this points to some kind of potential issue with the visual appearance of our cells.

Let's go to our storyboard to see if we can figure out what's going on.

All right, I've got my storyboard, let's open up our collection view and we have a template for our cell and the cell contains an image view.

And what we want is for that image view when the cell is focused to float up above our UI.

And now I'm taking a look at this I noticed that we forgot to switch on this adjust on ancestor focus option and this is what creates that floating effect and so that's probably our issue.

Let's rerun our application and see if that fixes it.

Awesome, now we can move between these cells, it clearly indicates which cell is currently focused with that floating effect so we're looking in better shape.

There is another issue with this allocation though, which is that I can't seem to focus on this more info button that's in this bottom shelf of our app and I don't really know what's going on there, there's nothing I can do from here.

So, let's go back into our application and see if we can figure out what's going on.

We're going to go to our mainViewController, set up breakpoints, let's trigger that breakpoints.

All right, so this is a great opportunity to use that new check focus ability method.

I'm going to type in P-O UIFocusDebugger.

What's great is that we get full autocomplete support and we're allowed to invoke this, we need a Swift code.

Let's check the focusability of our more info button which I have an outlet referenced to.

All right, so it detected an issue and what this is telling us is that the item is being visually occluded, that's visually blocked by some other item on the screen.

And it really helpfully even printed out a reference to the item that it thinks is causing the problem.

Let's go back to our storyboard to see if we can figure out what's going on here.

All right, I'm going to go to our mainViewController.

All right, so here's our more info button.

So, it said that we were being visually occluded by some other item, but I can obviously see this more info button here so what's going on.

Well now I'm looking at the storyboard I notice this other sibling view to our more info button and this is a semitransparent white view that we use as the background of the bottom shelf in our application.

And I'm noticing that this view was accidentally placed on top of our more info button, but that was really hard to see because it's semitransparent.

So, let's try dragging this just below our more info button and relaunch our application and see if that fixes the issue.

All right, let's try that again.

All right, make that a little bit bigger.

Okay now I can go and focus on that more info button.

As our application is looking in better shape now we can start to move on to the more important work of building out our new features.

Let's [inaudible] back to the presentation.

[ Applause ]

So that's just a quick example of how UIFocusDebugger helped us to identify those kinds of simple mistakes that are easy to fix, but that can nonetheless waste a ton of time during application development as we try to track them down.

So that's focused debugging in tvOS 11.

To recap our session for today.

Please take advantage of the new focus update notifications and the protocol extension methods that we're adding in tvOS 11 in order to accomplish common tasks with less code.

You can use the new animation APIs and the custom sound APIs that Jon was talking about earlier in this session in order to provide a more immersive experience for your apps and games.

Definitely check out the new focus interaction support in SceneKit.

This is a great way to add interactivity to your SceneKit games.

And then finally, use the new focus debugging tools to save yourself a lot of time.

For more information on today's session, you can go to developer.apple.com.

If you're interested in learning more about developing for tvOS you can check out some of these related sessions that we're putting on this week.

Thank you very much.

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