Using Parallax images and Focus Guide with tvOS (Part 2/3)

Teddy Georgieva
Dev Labs
Published in
10 min readMay 31, 2017

--

Overview

If you’ve ever developed an app for tvOS you’ve inevitably noticed the limitation of moving the focus only on the horizontal and vertical axes. That means one of two things: you either have to cater every design to accommodate this limitation or you have to find a way to expand the focus engine’s field of vision.

Designing for the tvOS has always presented a challenge because designers rurally have any knowledge to how the system works. It’s not their prerogative — their job is to present a user friendly, accessible and instinctive design, not think about how it’s realized. It’s the developer’s job to make said design come to life.

So, in the words of the most famous detective in recent literary history: “Once you eliminate the impossible, whatever remains, no matter how improbable, must be the truth.”

In other words, since the design can’t come to you, you have to go to the design. Fortunately, taking control of the Focus engine and creating an attractively presented interface isn’t as difficult a task as it might seem.

Today I will show you how to use Focus Guides to dominate the user’s interactions with your app and break out of the oh, so inconvenient two-dimensional navigational structure. I will also show you an easy way to use Parallax images and focus on the seamlessly, all without the need a table or a collection view.

Prerequisites

Like last time, at least a basic knowledge in Swift is required. I will be using delegates to implement the catalogue’s cell selection, so knowing and understanding delegates is required. I will give a few words in explanation, however, this tutorial will not focus on how delegation works, or how to use the storyboard or it’s constraints. I will be using Xcode 8 and Swift 3.

This is a continuation of Creating a catalogue style TableView with tvOS (Part 1/3). If you’ve already gone thorough that tutorial you can use the catalogue project we made from there.

If not, you can find it and download it here.

Let’s get started.

Parallax images — how to use them

In the Creating a catalogue style TableView with tvOS (Part 1/3) tutorial you created and used a Parallax image in a collection view cell. All you had to do is set the adjustImageWhenAncestorFocused to true. That basically tells the image view to use the parallax animation when the UIImageView’s ancestor is focused. In your catalogue that is the collection view’s cell. It’s focusable by default and when we redirected the focus from the table’s cell to the collection view it was easy for the Focus engine to animate the image.

But what if we have just one image we want to show?

Well, you can still add a table view and give it just one cell, but instantiating a whole table view for one cell seems like quite the waste of resources.

But we still want to use our Parallax image and use the neat adjustImageWhenAncestorFocused. So let’s apply a nifty trick.

Head over to the catalogue-app. Open the Main.storyboard and add a new controller to the right of the current one. Once you have it all setup, we’ll add a few elements in accordance with the episode information we want to show.

Add two labels, one to hold the title of the episode, another to hold a short synopsis. Then, add two buttons to play the video in different resolutions. You should have something like this:

Now, you want to also show a still from the episode, something juicy and intriguing to present to the user and spike his interest. You also want to keep your app consistent, so showing a Parallax image is a given. But you don’t want your app to become boggled down by too many heavy hitters, like UITableView.

Instead, add a plan UIView to your screen.

Now, you will put your UIImageView inside the UIView you just added. Align it so it allows for some space, you remember the Parallax effect scales your image. Select the UIImageView and in the property list check Adjust on ancestor focus (for XCode 8.2 and below the property is Focus — Adjust image when focused). Select your cover parallax image as the UIImageView’s image in the Attributes Inspector.

This should make the UIImageView focusable whenever the UIView it’s inside is focused.

You’re probably wondering why you should go through all that trouble?

Because UIImageView can’t focus in a linear way. It needs an ancestor to use the in-build Parallax effect. For completely custom animation you can subclass UIImageView and override onFocusUpdate(). For this tutorial, we’ll focus on the build-in animation.

Making the UIView focusable requires you to create a custom view by subclassing UIView and then linking it to the storyboard element.

As you see, there is no difference between this workflow on iOS or on tvOS.

Making a custom view focusable

Create a new class in your project folder. Name the class EpisodeCoverView.

All you need to do in this class is to override canBecomeFocused. This property is set to false by default. Every time the Focus Engine starts to navigate through your views, it asks each of them for this property’s value.

If it’s false, they are skipped and any override to the didUpdateFocus method is ignored.

If it’s true the view is focused and all related animations are applied.

For our EpisodeCoverView to become focused and animate we need to return try for canBecomeFocused.

 override var canBecomeFocused: Bool {   return true }

That’s it. Now your view can get focused and present the Parallax effect. Go ahead to your storyboard and declare the view that holds the UIImageView to be of the type of the new class you just created — EpisodeCoverView.

Let’s add the logic to open and present your new view controller on screen. This is simple and works the same way as you would do it in iOS.

First, you need to create a new class for your episode details view controller in the storyboard.

Create a new UIViewController and name it EpisodeDetailsViewController. Then in the Identity inspector in Main.storyboard set the name of the class for the second view controller.

The next step is to create a segue to open the EpisodeDetailsViewController.

Create a new segue in your Main.storyboard as you normally would — CTRL + drag from ViewController to EpisodeDetailsViewController. Choose Show as the type for the segue. Name it ShowEpisodeDetails.

This new segue needs to be performed by your ViewController each type an episode is selected.

Since our cells, however, are the collection view’s cells we need to create a delegate from the CatalogueTableViewCell which your ViewController will implement in order to intercept the selection event.

Open CatalogueTableViewCell and above the class declaration add:

 protocol CatalogueTableViewCellDelegate: class {  func didSelectItem(inCell cell: CatalogueCollectionViewCell) }

Restricting the delegate’s targets to classes only prevents strong references to the delegate from causing memory leaks.

Now, declare the delegate property:

internal var delegate: CatalogueTableViewCellDelegate?

and call on the didSelectItem method in the collection view delegate

 // MARK: - UICollectionViewDelegate methods extension CatalogueTableViewCell: UICollectionViewDelegate {  func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {   if let _cell = collectionView.cellForItem(at: indexPath) as? CatalogueCollectionViewCell {   delegate?.didSelectItem(inCell: _cell)  }  }}

Then head over to the ViewController class so you can implement this delegate and use it to perform the segue you created.

 // MARK: - CatalogueTableViewCellDelegate methods  extension ViewController: CatalogueTableViewCellDelegate {  func didSelectItem(inCell cell: CatalogueCollectionViewCell) {   performSegue(withIdentifier: "ShowEpisodeDetails", sender: nil)  }}

And don’t forget to make your table cell initiate the delegate. Add this line to your cellForRowAt indexPath method after declaring the cell:

_cell.delegate = self

And done. Now if you run your project you’ll be able to open up any of the cells and see you new screen.

OK, so your cell is selected, you screen opens and you can focus your buttons. But what about your image? It was supposed to be focusable and even use that sweet Parallax effect.

Well, you’ll notice the out image view doesn’t quite make it to the line of sight of the buttons.

The red dotted rectangle shows you the line of sigh the buttons have. They see each other, because they are aligned on that line. But the image is above it so when you try to navigate to the right of it, the Focus engine can’t a view to focus on.

Note: Getting this snapshot is fairly easy. You need to have didUpdateFocus overridden in the view controller where you need the snapshot, in this case EpisodeDetailsViewController. This method is called every time a new focus change is evoked. So say the currently focused view is the left button and you initiate a movement to the right. The breakpoint inside the method is hit. When that happened, open the Variables View on the bottom of the page, select the context and then Quick Look.

Using Focus Guides

So how can you make your button “see” your image?

Basically, you need to redirect the focus. You’ll create a “fake” view, one that will always stay on the screen, invisible, and use it to guide your focus.

In a nutshell, that’s what focus Guides are. Invisible views that are always present, but invisible, and help you stir the focus in the right direction. You set them up just like views, only you anchor them to existing ones on the screen. For example, if you anchor a guide’s height anchor to the image view’s height anchor, the guide’s frame will be as heigh as the image view. It’s good to make your guides’s frames match those of the view they are representing to keep the user’s experience consistent sine the speed of the focus change depends on the frame of the views it switches between.

For your episode details you’ll need two focus guides. The first one will be used to guide your focus from the second button to the image. The second — to guide from your image to your button.

First you need access to your outlets in the EpisodeDetailsViewController. A property for one of the buttons is more then enough since you’ll be using their frames to position your Guide in the view. It really doesn’t matter which button you choose since they are aligned next to each other.

@IBOutlet weak var episodeCoverHolder: EpisodeCoverView!@IBOutlet weak var playButton: UIButton!

Wire up those outlets. You’ll need them to give frames to your Focus Guides.

fileprivate var episodeCoverFocusGuide: UIFocusGuide! {  didSet {    //1   view.addLayoutGuide(episodeCoverFocusGuide)   //2   episodeCoverFocusGuide.leftAnchor.constraint(equalTo: episodeCoverHolder.leftAnchor).isActive = true   //3   episodeCoverFocusGuide.topAnchor.constraint(equalTo: playButton.topAnchor).isActive = true//4   episodeCoverFocusGuide.widthAnchor.constraint(equalTo: episodeCoverHolder.widthAnchor).isActive = true   //5     episodeCoverFocusGuide.heightAnchor.constraint(equalTo:   episodeCoverHolder.heightAnchor).isActive = true   //6   episodeCoverFocusGuide.preferredFocusEnvironments = [episodeCoverHolder]  }}

Let’s break it down:

  1. You add your newly created focus guide to your views layout guide. The LayoutGuide holds all of your focus guides. It’s important to add the focus guide first, before trying to anchor it to any other view. If you try to anchor the guide before you’ve added it to the view Xcode will complain, saying you’re trying to anchor views with no common ancestor, since the guide is technically not in the view’s hierarchy, yet.
  2. You set the left anchor of the guide to align with the left anchor of your image view. Fundamentally what the says is “align both views to the left”.
  3. You set the top anchor to align with the play button’s top anchor. That way you make sure the focus guide can be seen but the button on it’s horizontal axis.

4 & 5. You set the width and height of the guide to match those of the image view.

6. The preferredFocusEnvironments variable indicates the view the focus guides should redirect the focus to once the Focus Guide receives it. In this case we want it to redirect to our image view.

When added, this focus guides looks like this:

In simple layout terms, your guide is your image view moved along the y axis until it’s aligned with the button’s y origin. That way the Focus engine “sees” your image view.

Now, try to do the the other focus guide by yourself. It should look something like this:

Note: If you couldn’t make the second Guide, you can download the finished project from here and use it directly.

And that’s it. If you run your project you should be able to freely navigate through your buttons as well as your image view.

What’s next?

Now that you know a little more about Parallax images and Focus Guides go ahead and try some designs of your own.

The finished project can be found here and it contains a few more design changes you might enjoy. Go check it out and try some alterations of your own.

We’ll finish off the project in Using top shelf layouts with tvOS (Part 3/3), by implementing some neat top shelf ides. If you want to learn more about setting up your top shelf, and providing the user fast access to your app’s content, check it out.

--

--

Comic book fan. DIY enthusiast. iOS Developer. Writer. Aspiring designer.