Focus Engine For SpriteKit tvOS

  • 5 Min. Read.

With the introduction of tvOS, Apple bundled a fancy Focus Engine for users to navigate through the user interface. The Focus Engine is the system within UIKit that controls focus & focus movement, but unfortunately there is no equivalent for SpriteKit – which kinda makes sense since the complexity of a SpriteKit game can become immense.

While porting over a card game to tvOS, I struggled with creating appropriate input methods for the user to select a card. After some time I’d gotten to a point where my own focus engine would work ‘fine‘, but still didn’t feel quite the same as the Apple one – and that bothered me. It’s in the small things really – the inertia that gets applied to your scrolling gesture in the UIKit Focus Engine feels so natural and smooth. I wanted to achieve that level of smoothness in my SpriteKit game.

TLDR: Focus Engine forwarder for SpriteKit will give you a 1:1 implementation of the UIKit Focus Engine in SpriteKit. It also comes with a small demo, take a look!

Creating the focus forwarder

After some brainstorming and hacking away, I found an approach that’d fit great in my game. The theory of this approach is pretty simple:

  • Capture the UIKit Focus Engine scrolling
  • Pass it through to my SpriteKit scene

This solution is in its very essence a simple joystick.

Capturing the UIKit Focus Engine scrolling

To capture the UIKit Focus Engine scrolling, I set up 4 UIButtons (or custom views) like this:

Focus Forwarder Buttons
Focus Forwarder Buttons

We get the focus functionality for free (or with minor setup), since this is UIKit and the Focus Engine takes care of this for us.

Focus Forwarder Buttons
Focus Forwarder Buttons

But for this joystick to work correctly, the rows and columns should wrap infinitely.

Focus Forwarder Button Wrapping
Focus Forwarder Button Wrapping

To achieve this, we have to work with UIFocusGuides, which are ‘invisible’ guides to help the Focus Engine do its job.

Visual Representation of the FocusGuides
Visual Representation of the FocusGuides

And with this structure in place, the last thing to do is actually tell our FocusGuides which view should become focussed once we hit them. We do this by setting the preferredFocusedView property on the FocusGuide.

Visual Representation of the FocusGuides
Visual Representation of the FocusGuides

And now we can infinitely swipe in all directions, how convenient!

Hiding the CCFocusForwarder

The Focus Engine is pretty smart and ignores invisible elements, so we actually have to trick it, by setting the opacity to a very low value. It’ll appear invisible on screen, but the Focus Engine will be fooled & still ‘see’ the buttons.

Passing the values to the SpriteKit scene

Now that the hard work has been taken care of, we simply have to get the values back in the SpriteKit scene. The only value we actually need is the direction in which we should be moving, and the UIFocusUpdateContext provides this value for us.

We have an instance variable called focusHeading which will call the delegate method once it’s been set.

The delegate is an instance that conforms to CCFocusForwarderDelegate, which has exactly 1 method:

In our delegate we can then handle the directional movement of the scrolling – and the cool thing is, the Focus Engine takes care of the inertia for us, so we have a 1:1 implementation of the Focus Engine in SpriteKit!

You’re still responsible for correctly deciding which is the next object that has to get focus, an example implementation looks like this:

Changing the focus sounds

tvOS automatically plays a small beep sound when it focusses an element – and each element has its own unique beep. We can distinguish different sounds between the following 2 elements:

  • UIButton
  • UIView / UIImageView

There are 2 more objects, but developers can’t reproduce these sounds with custom objects:

  • Keyboard
  • App Icons

Unfortunately there is no way (in iOS 9.1) for us to mute this beep. There is a private API available for the _UIFocusEngine class, which has a property _Bool _playsSoundOnFocusChange; You could tinker with this using method swizzling, but there’s a chance of your game being rejected for using private API’s. Example in Objective-C:

Closing remarks

This blogpost was a quick & simple rundown of the CCFocusForwarder class available on Github. Take a look at the example on Github to see it in action in a small demo.

Focus Forwarding Example For SpriteKit
Focus Forwarding Example For SpriteKit

 

A concept by Wonderlus