Allow me to begin with quick run-through of tvOS's Focus Engine and Focus Guides:
Focus Engine: The Basics
With the advent of tvOS, Apple moved from an in-the-hands touchscreen to a TV 10 feet away. Now that you could no longer touch the environment with your finger, Apple needed a new paradigm for user interaction.
Enter the Focus Engine.
We can think of Focus as a spotlight, directing the user's attention toward a particular element on screen. Views can be highlighted, or Focused, and the user can move Focus around different elements of the app using the remote control. It's like a mouse cursor but with only a discrete number of options.
But how does the Focus Engine work underneath the hood?
When a user swipes on the remote control's touch surface, the Focus Engine looks in the direction of the swipe for the next view to which to move Focus. If it "sees" one, it moves Focus there, if it doesn't see one, it doesn't move Focus. Simple as that.
As an example, see the simple 3-button app below. When the Top Left button is in Focus and the user swipes right, the Focus Engine will move Focus to the Top Right Button, as expected.
At this point, a swipe down would do nothing. The Focus Engine looks below the Top Right Button and sees no views, so it does not move Focus. The only way to move to the Bottom Left Button would be to go back and down through the Top Left Button.
However, most users would likely expect a swipe down to move Focus to the Bottom Left Button.
This is where Focus Guides come in.
As in our example, there are scenarios where a user might expect the Focus Engine to behave beyond its default behavior. We can help the Focus Engine out by making use of Focus Guides.
A Focus Guide is an invisble layout guide that helps the Focus Engine know where to move Focus.
In our example, we can place a Focus Guide in the bottom right position:
Now, when the Focus Engine looks down, it will find the Focus Guide...
...and the Focus Guide will direct Focus to the Bottom Left Button
First, we add the Focus Guide to the view.
// Create the Focus Guide and add it to the view var focusGuide = UIFocusGuide() view.addLayoutGuide(focusGuide) // Anchor the Focus Guide focusGuide.widthAnchor.constraintEqualToAnchor(topRightButton.widthAnchor).active = true focusGuide.heightAnchor.constraintEqualToAnchor(bottomLeftButton.heightAnchor).active = true focusGuide.topAnchor.constraintEqualToAnchor(bottomLeftButton.topAnchor).active = true focusGuide.leftAnchor.constraintEqualToAnchor(topRightButton.leftAnchor).active = true
Then, to make the Focus Guide direct Focus to the Bottom Left Button, we set the Focus Guide's
preferredFocusView property; this is the view to which the Focus Guide will direct Focus.
// Set preferred focus focusGuide.preferredFocusedView = bottomLeftButton
So now, with the help of our strategcally-placed Focus Guide, a swipe down will move Focus down to the Bottom Left Button, as the user would expect.
That's a basic rundown on how the Focus Engine and Focus Guides work.
But what if they're not working? How do you debug a Focus Guide issue?
Apple provides 2 solutions:
1. Quick Look
Quick Look is a visual tool to debug Focus Engine and Focus Guide problems.
To activate it, you have to catch a break in the method
didUpdateFocusInContext(context, withAnimationCoordinator coordinator); this gets called every time Focus is moved. Then, in the Varibles View in the bottom-left section of Xcode, highlight the
context variable, and click the eye icon at the bottom (or press the spacebar).
This brings up Quick Look, which looks like:
Quick Look shows our 3 buttons, with the button currently in Focus higlighted in red. The direction in which the Focus Engine is "looking" is shown in light red, and a light red border is drawn around the view to which Focus is about to move (which just so happens to be the Focus Guide). Focus Guides are highlighted in blue.
You can open the image in Preview and save it, email it to other developers, etc.
Quick Look definitely provides a useful debugging tool, but it has its flaws.
The biggest flaw is that in order to activate Quick Look, you have to catch a break in
didUpdateFocusInContext(), but of course if the very issue you're trying to debug makes it so Focus never moves,
didUpdateFocusInContext() never gets called and the breakpoint will never be hit.
That, Ladies and Gents, is what we call a Catch-22.
Apple also offers the (very descriptively named) LLDB command called
_whyIsThisViewNotFocusable as another Focus debug tool.
To activate this command, you need to catch a break anywhere in your code (or even just pause it), and run the command
_whyIsThisViewNotFocusable in the LLDB command line debugger, like so:
As you may have noticed, the command is in Objective-C, so it won't work if your project is written in Swift. The Swift command is:
This will print out a description of some potential reasons for why Focus won't move to your view. See example below:
In this particular example, the simple solution would be to just go the storyboard and enable user interaction on the button.
_whyIsThisViewNotFocusable has its share of flaws as well, some of which include:
- Not visual
Only works for views not focusable for very specific reasons such as:
- View is hidden
- View is obscured by another View
For example, if none of the above reasons hold true,
_whyIsThisViewNotFocusablereturns a message that's not particularly helpful:
Visual Focus Guides
To help fill some of the gaps left by Apple's Focus Guide debugging solutions, I created a 3rd tool1 called Visual Focus Guides. They're visual (as the name suggests), and you can activate them with a method within your code, or you can activate them anywhere via the LLDB debugger.
They make your Focus Guides visible (almost) in real-time, and they're color-coded to match each Focus Guide to its
preferredFocusedView. I've found that debugging a problem with Focus Guides is much easier when you can see their location with your own two eyes.
The tool is packaged as an external library, and once installed, can be activated via the following methods:
// Show focus guides yourView.showAllVisualFocusGuides() // Hide focus guides yourView.hideAllVisualFocusGuides()
When activated, a Visual Focus Guide (shown below in purple) shows the location of a Focus Guide with a solid color2. It also displays a thin border in the same color around the Focus Guide's corresponding
If there are multiple Focus Guides that share a single
preferredFocusedView, they will be shown in the same color2, as shown here in teal:
In order to activate Visual Focus Guides from within LLDB, simply use the following commands:
(lldb) po yourView.showAllVisualFocusGuides() (lldb) e CATransaction.flush() //Re-draws view
To deactivate them:
(lldb) po yourView.hideAllVisualFocusGuides() (lldb) e CATransaction.flush() //Re-draws view
The Visual Focus Guide library is available through CocoaPods.
Simply add the following lines to your Podfile and run a
pod install command from Terminal:
source 'https://github.com/CocoaPods/Specs.git' platform :tvos, '8.0' use_frameworks! pod 'VisualFocusGuides', '~> 0.1'
Hopefully this helps you make some cool apps.