Today in part 8 of my “Making Apple TV Apps” series, we learn how we can diagnose and fix focus problems when they occur. I’m also ramping up the posts in this series to two posts per day in anticipation of its live presentation Thursday, April 21st, at my local CocoaHeads meeting. I hope to see you there!
NOTE: Check out my previous post first if you’re not familiar with the Apple TV focus engine.
The Unfocusable View
As you develop your tvOS app, you’re bound to run into an issue with the focus engine’s behavior eventually. Maybe a view you’re expecting to receive focus is skipped. Or maybe you can’t move focus in the direction of a UI element at all. Regardless of the details, you’re going to need to know how to deal with the issue.
Lets review common reasons for a view being unfocusable, how to easily test those conditions, and even how to view a live visual diagram of what the focus engine sees at any given time.
Cure for the Common Code
There are many possible reasons why a view can’t be focused:
- The view’s canBecomeFocused method returns false
- The view’s hidden property is set to true
- The view’s alpha property is set to 0
- The view’s user interaction is disabled
- The view is obscured by another view on top of it
- One of the view’s superviews meets some of these conditions
Thankfully, we’re not on our own when it comes to checking all these possibilities. Apple added a hidden method to UIView in tvOS called _whyIsThisViewNotFocusable. It tests all these possible issues.
To get an idea what is going on, print out the method’s response in the Xcode console using this syntax for Objective-C…
po [(UIView *)0x148db5234 _whyIsThisViewNotFocusable]
…or one of these syntaxes for Swift, depending on the version of Swift you’re using.
Swift 2.1 or older:
po self.customView.performSelector(Selector("_whyIsThisViewNotFocusable"))
Swift 2.2+:
po self.customView.performSelector(#selector(_whyIsThisViewNotFocusable))
In most cases, the resulting output will lead you to the problem source. Here are some output examples that you may see when running the method. My favorite is the last example. In this case, the view you were concerned with is fine, but one of its superviews is preventing it from receiving focus. The output will tell you which superview has the problem and what its problem is. Nice!
ISSUE: This view has userInteractionEnabled set to NO. Views must allow user interaction to be focusable. ISSUE: This view returns NO from -canBecomeFocused. ISSUE: One or more ancestors are not eligible for focus, preventing this view from being focusable. Details: <ExampleAncestorView 0x148db5810>: ISSUE: This view has userInteractionEnabled set to NO. Views must allow user interaction to be focusable.
Seeing the Focus Engine’s View of Things
If you’ve run into a focus problem and the output of whyIsThisViewNotFocusable didn’t help, its time to pull out the big guns. Xcode also provides a Quick Look tool that provides a visual representation of what the focus engine sees when it evaluates focus changes.
Wielding this tool is pretty easy. First, set a breakpoint in shouldUpdateFocusInContext and/or didUpdateFocusInContext: withAnimationCoordinator. You can temporarily add a default implementation of one of these methods if you haven’t needed to customize their behavior yet. When you’re choosing where to add the breakpoint at, keep in mind that shouldUpdateFocusInContext will be called before a focus change, while didUpdateFocusInContext: withAnimationCoordinator will be called after it. You may need a breakpoint in one or both of these functions, depending on the behavior you’re trying to inspect and understand.
After launching your app and hitting your breakpoint, you should see the parameters that are passed into either method listed in Xcode’s debug window. Select the context parameter and click the Quick Look icon. Its the icon that looks like an eye.
After clicking the Quick Look icon, an image similar to the one below should appear on your screen.
The item that is currently focused (or previously focused, depending on the function you set the breakpoint in) is highlighted in red.
The direction that the focus engine is searching for the next item to be focused is indicated with a dotted red line. The direction to be searched is based on the direction the user indicated they wanted the focus moved in. In our sample image, the focus engine is searching to the right of the selected item.
Any focusable views found in the search path are highlighted in purple. Different patterns are used for each view in case they overlap one another. This makes it easier to determine what elements the focus engine considers to be independent elements.
Do you remember UI focus guides, our helpful buddies from my last post? While UIFocusGuide instances aren’t normally visible on the screen, they are highlighted in blue in the Quick Look window, as seen in the sample image below.
Last Call
Apple did a great job providing tools that make simple work of any focus issues we may encounter. The _whyIsThisViewNotFocusable method can answer all the common questions in a flash, and the Quick Look tool takes a potentially complex process and makes it easy to see exactly what the focus engine perceives from the current UI. You should now be well-armed to tackle any focus issue after reading this article.
In the next part of my series, we’ll be exploring the other two new frameworks added to tvOS: TVJS and TVML. Are you ready to build a tvOS app using a brand new approach? I hope so! Hurry back soon, or sign up for a fresh copy of that and every new article to fly into your inbox the minute they’re available. Until then, keep calm and code on.
Leave a Reply