UPDATE:tvOS 11 now supports non-roundrect buttons! You don’t need to do this any more to get circular buttons — yay! However: if you’re still interested in building your own Focus Effect by using UIInterpolatingMotionEffect, read on!
In my current side-project, I’m making a tvOS app. One of the screens features a handful of circular buttons. Unfortunately, when the tvOS Focus Engine renders these buttons, they get really awful shadows and focus states, that look a bit like this:
The system-standard UIButton looks awful with circular images.
I had the good fortune of attending Apple’s tvOS Tech Talk in Seattle this winter - which meant I got to ask a few folks in the Q&A lab! They told me that, unfortunately, tvOS doesn’t allow non-roundrect focus effects: the system-standard focused state are pretty “simple” and aren’t clever enough (yet) to alter their shape depending on the alpha-channel of the image in the UIButton.
We needed to roll our own custom focus effect - but fortunately, it turned out to be pretty straightforward!
Here’s what the finished product looks like:
Ah, much better! (Apologies for the GIF compression; it’s rasterizing the shadows.)
From the above list, we know we’re going to need some transforms, a shadow, a parallax tilt, a parallax shift, a parallax “white glare”, and a parallax layered image. (Note: for my project’s design, I don’t need the glare or layered image - but I’ll have a footnote below outlining what I might try if I were building those effects.)
Really, there are two subgroups here: - a “focused style” (“what does this view look like when it’s focused?”) - and a “parallax style” (“how does this view tilt and shift around when it’s focused?”)
Let’s start by creating a FocusedStyle:
We can then create a protocol that allows any view or control to render with this style:
Now let’s create a CustomFocusableButton that conforms to our CustomFocusableViewType protocol. There’s a bit of code here to make the “select” animation work (when you click down on a button) - but outside of that, there’s not much here:
Next, let’s make a ParallaxStyle, which wraps our FocusedStyle along with the UIInterpolatingMotionEffects that compose into a parallax effect when you slide your thumb around.
So far, we’ve got FocusedStyle, ParallaxStyle, and a custom UIButton that implements CustomFocusableViewType and animates its UIControlState.Selected properly. Now we need a way to link up our UIMotionEffectGroup and FocusedStyle to the didUpdateFocusInContext(_:withAnimationCoordinator:) function in our UIView.
Enter the CustomFocusEffectCoordinator:
This class has the following responsibilities: - Links a set of CustomFocusableViewTypes to a ParallaxStyle, - Provides an easy way to update from a UIFocusUpdateContext and UIFocusAnimationCoordinator in the parent view. - Provides a way to tear down the effect.
Implementing in our UIView / UIViewController
Now for the fun part: hooking it all together in the UIViewController or UIView!
Ta-da! Now you have a nice Focus behavior on your custom views - and since it’s all composed by little protocols and style-structs, you can easily tailor the focus and parallax behavior to suit your needs.
One Last Thing
In my current project, we didn’t have a need for the white gloss or layered image aspects of the focused state. Using the above code, you could probably extend ParallaxStyle and CustomFocusableViewType to implement these behaviors.
But, a word of caution: if you’re going to implement these bits, spend extra time polishing ‘em, because it’s no good when custom UI attempts to mimic the native platform but gets stuck in the Uncanny Valley. If you don’t fully dial-in the gloss and layered image effects, you may find that your UI feels a bit out-of-place, like a non-native app running on iOS.