Mastering UIKit on tvOS

Session 210 WWDC 2016

Learn how to make your tvOS interface more dynamic, intuitive, and high-performing with tips and tricks learned in this session.

[ Music ]

[ Applause ]

Welcome to Mastering UIKit on tvOS.

My name's Justin.

I work on UIKit.

And I am here to show you how you can go beyond the basics and really take your tvOS app to the next level.

So to do that, there's a few things that I want to talk to you about today.

And the first is event handling.

What kind of events is your app going to receive, and what are the best ways for you to handle those events?

Then I'd like to talk about layered images.

This is a user interface element that's unique to tvOS, and we'll show you how to really take advantage of it.

Thirdly, we'll talk about scrolling.

Scrolling on tvOS works a little bit differently than scrolling on iOS.

And we'll show you how you really take control of scrolling.

And finally, we'll talk about text input and how you can use the system keyboard to accept text from users.

So let's get started and dive right into event handling.

So when it comes to event handling, there are a few overall best practices that I want to share with you right off the bat.

And the first is, is that when it comes to allowing your users to navigate your application and to move focus from one item to another, you don't need to write any of your own event handling code at all.

The focus engine is going take care of all of that for you, including handling different kinds of events based on what kind of input device the user is currently using.

So it'll take care of all of that for you.

Secondly, if you decide you do need to start handling events, I recommend that you start with UIGestureRecognizer.

Not only to have a really expressive API, but it'll also help ensure all of the gestures and interactions in your app are going to be consistent across all the system apps and other apps.

And finally, as you are designing the interaction model for your application, keep in mind that not every input device can generate every kind of event.

So for example, a user might be using a game controller to navigate to your application.

And game controllers can't generate touch events.

So you might not want to use touch events as the only way to interact with your application.

So keep that in mind as you are designing your app.

So speaking of touches, we have touches on tvOS using the touch surface on the Siri remote.

And we deliver them to you using UITouch, which is an API that you might recognize from iOS.

And a touch represents the contact between a user's finger and that surface on the remote.

This is just like the iOS version.

It has the location and all the other properties that you are used to.

But there is one important difference between touches on tvOS and touches on iOS.

A touch on iOS is happening directly onto the user interface.

The user is physically touching the UI.

That's not the case on tvOS.

The user interface is 10 feet away, and the user is touching their remote.

And so we bucket these into two different categories.

We say the ones on iOS are direct touches, and the ones tvOS are considered indirect touches.

And we have this touch type called UITouchTypeIndirect to help you tell the difference between the two of those.

And because of this indirection, if you were to ask the UITouch, "What's your location onscreen?"

on a direct touch interface that's a pretty obvious answer.

We give you a location that maps exactly where the user's finger is on the screen.

But on tvOS, the user isn't touching the screen.

What location should we report for the touch?

And so what we do in this case is that no matter where the user's finger begins touching the remote, we always deliver the touch to your application as if it had begun directly in the center of the focused view.

And this is really great, because it means that any gesture recognizers or event handling logic that you might have attached to the focused view will always have an opportunity to participate in the gesture.

One thing that we don't expose is the absolute location of the user's finger on the track pad.

And there is a few reasons why we do that, but one of the really important ones is that that we want to discourage pointer-based UIs on tvOS.

We don't want to see anything like mouse cursors or anything like that.

We'd really rather that applications stick to the focus interaction model that we have created.

So in addition to touches, there is also another kind of event that you'll get on tvOS; and these are called presses.

And presses are delivered using the classical UIPress.

And a press represents an actual physical hardware button that the user is interacting with.

And not all of those buttons are simply pressed or not pressed.

Some buttons, like those on game controllers, are pressure sensitive.

But we can actually detect how firmly the user is pressing the button, and we can deliver that information to you.

You can recognize presses using UIGestureRecognizer, including things like TapGestureRecognizer and LongPressGestureRecognizer.

And we have low-level press handling events that follow the same pattern as a low-level touch handling event.

So we have pressesBegan, which fires as soon as the user begins depressing the button.

We have pressesChanged, which will be delivered if it's a pressure-sensitive button and the amount of pressure changes.

And we also have pressesEnded and pressesCancelled.

And just like touchesCancelled, yes you really do have to implement it.

There's one thing to know about these methods, which is that press events because they don't really have a location they get delivered directly to the focus view.

And they go up the responder chain from there.

They won't go to any child views of the focus views.

So keep that in mind when you are deciding what view to attach gesture recognizers to.

Okay, so we are talking a lot about presses.

Let's go through some of the press types that are available to you and what buttons will trigger them on various input devices.

So I want to show you the Siri remote, the Apple remote from previous-generation Apple TVs, and I want to show you game controllers.

And I don't have room on the slides here, but keep in mind presses can also be generated from programmable universal remotes and also from Bluetooth keyboards.

So there is a lot of ways to get presses.

Probably the most common press type you'll encounter is UIPressTypeSelect.

And this is the press type that represents that the user wants to activate a control or select on a CollectionView cell, something like that.

And on the Siri remote, that's the button that's actually underneath the touch surface on the remote.

On the Apple remote, that's the silver circle.

And on game controllers we use the A button to generate this press type.

Another really common press type you'll encounter is UIPressTypeMenu.

And this is the press type that indicates the user wants to go back.

They want to dismiss a presented view controller or even dismiss your application entirely and return to the Home screen.

And in all three of these on the screen, that'll be the button that says Menu.

And on game controllers, we will also use the B button.

And finally, there is UIPressTypePlayPause.

And this is obviously useful for pausing or playing content, but it can also be used as sort of a shortcut in addition to Select.

So if the users focus on, say, a movie poster, pressing the Select button might show the details for the movie.

But pressing Play/Pause will just jump straight into playback.

And so that's the icon that has the or the button that has the Play/Pause icon on it.

And on game controllers we'll use the X button to generate that press type.

Okay, so to round things out there is just a few more press types, and these are directional presses.

So we have up, down, left, and right arrow presses that can be generated.

On the Apple remote we use the four directional buttons.

On game controllers there is lots of ways to get these.

So we'll use the D pad.

We'll also use the directional analog stick.

And we'll even generate left and right presses using the shoulder buttons on the game controller.

Now there aren't any discrete hardware buttons for these arrows on the serial remote, so you might think that it's not possible to generate these there but that's actually not true.

If you do a touch tap you are not pressing all the way down on the Select button but just touching on these four cardinal points on the surface.

Then UIKit will detect that and actually generate arrow presses for you.

And this is a really convenient way to navigate through your user interface exactly one item at a time.

So just because the user is using the Siri remote, that doesn't mean that they can't generate arrow presses.

Okay, so I promised you that you can use gesture recognizers to recognize these.

And I won't go through these in detail, but I just want to show you a few quick examples.

You know, we can use the TapGestureRecognizer there on the top.

We can even do a long press just like you might do to go into the editing mode on the tvOS Home screen.

And there at the bottom I can even adjust the number of taps required.

So now I am listening for a double tap on the Select button.

Okay, let's talk about one button in particular, and that's the Menu button.

The Menu button has some interesting behavior on tvOS.

It kind of has to play two roles.

Not only does it need to be useful inside of your application, because we need to do things like dismiss view controllers or pop things off of a UINavigationController.

But it also needs to have system-level behavior.

And eventually your app needs to dismiss be dismissed so the user can return to the Home screen.

And your user is going to be really frustrated if that's not possible.

And we can think that this is so important, that this is something that App Review looks for specifically.

So if your application can't be exited by pressing the Menu button, you might fail App Review.

And we don't want that to happen.

So how does this work?

How do we get it so that the Menu button can both be captured by your application but also used by the system?

Well the technical thing that needs to happen in order for your app to gracefully resign and go back to the Home screen is that when the Menu button is released, the pressesEnded event needs to go all the way up the responder chain if you are received by UIApplication.

If that doesn't happen, your application won't resign.

Now sometimes that's exactly what you want.

So if you are deep into UINavigationController, and the user presses the Menu button, they don't want to resign your app; they just want to go back to the previous view controller.

So in that case, there might be a TapGestureRecognizer that's listening for the Menu button.

And if that recognizer begins to recognize, it'll send pressesCancelled to the responder chain.

And in that case the application won't exit, and then UINavigationController can hop off that view controller.

So it's okay to not always send pressesEnded to your application.

You just need to make sure that it is possible when it becomes at the appropriate time.

So to do this correctly, we obviously recommend that you start with gesture recognizers.

And when you no longer are interested in handling the Menu button, you need to make sure that GestureRecognizer gets out of the way.

And so one way to do that is just remove the GestureRecognizer from the view.

You also disable the gesture.

So gesture recognizers have a property called enabled.

And if you set that to false, then the gesture will get out of the way.

And if you don't want to do either one of those, there is UIGestureRecognizer delegate API called gestureRecognizerShouldBegin.

And you can implement this, and then for every press of the Menu button you get the opportunity to decide right then whether or not you want to handle the gesture.

If you are implementing the lower-level press handling methods, in your pressesEnded implementation decide whether or not you want to handle the event.

And if you do want to handle the event, then don't call super.

But if you are not going to handle the event, then make sure that you do call super so that it can continue to go up the responder chain and UIApplication has the opportunity to receive it.

Finally, if you are writing a game, it's common that you only really have one view controller, which is the one that shows your game.

And so in that case you might want to look at using GCEventViewController.

Now it has this property, controllerUser InteractionEnabled.

And what this does is that when the user is navigating through your app using a game controller, not only are events going to the game controller framework, which you are probably already using your game.

But they also can generate UIKit events, like these presses and other things.

And when you are in the middle of game play, you are probably not interested in receiving the UIKit version of those events.

So if you set controllerUser InteractionEnabled to false during your game play, then pressing, say, the Menu button won't instantly exit your application.

And instead you'll be able to capture it and take care of it on your own.

But when the user gets back to the root menu of your game, you are going to want to turn this back to yes so that pressing the Menu button can exit your application.

So that is event handling.

Let's talk now about layered images.

Layered images are this user interface element that's really unique to tvOS.

And there are these images that you've probably seen in all the demos.

And they can have up to five layers within them, a parallaxing content that moves.

And these are required for application icons.

So your application icon on tvOS has to have at a minimum of two layers.

So you'll have to be at least a little familiar with this format if you are going to be on tvOS and they're interactive.

As the user moves their finger around on the Siri remote, the image moves in time with their finger.

And this is more than just eye candy.

This helps the user to realize that their inputs are being received and helps them to anticipate focus movement before it happens.

And they're animated.

So they have a lot of great animation that comes built-in, especially like when they become focused.

And we'll show you how you can coordinate those animations to really make your app come to life.

Okay, so let's talk about that interactivity.

So the video that I just showed you, that's a UIImageView.

And if the UIImageView becomes focused, then it'll automatically give you that great floating appearance if you have a layered image inside of that ImageView.

But it's often the case that a UI image is not the control that's actually becoming focused.

Images are typically a component of a larger control, and in that case the larger control is going to become focused.

So how do we get this floating appearance if the image isn't what's becoming focused?

Well it doesn't have to be focused.

We have this property that we've added to UIImageView called adjustsImage WhenAncestorfocused.

And if you set this to be true, then if any parent view of the image becomes focused, then the image will automatically get that floating appearance and begin reacting to the user's input.

And these images can also have a pressed-in state.

And so this is slightly different than the normal default state, where they are sitting back on the screen.

They kind of have this sort of smooshed-in appearance as if the user is physically pressing them down.

And you can trigger this manually by setting the isHighlighted property on your ImageView to be either true or false, and that'll turn on or off that effect.

And you might want to do this in your control subclass when the user starts pressing the Select button, for example.

But there are a few cases where we cover this for you and you won't have to do this manually at all.

And those are images that are inside UICollectionViewCells and images that are inside of custom UIButton.

In those cases you don't have to manage isHighlighted property at all; we'll take care of that for you.

Okay, so we talked about animations.

So let's look at that a little more closely.

So one of the things that happens when these images become, in their floating appearances they get bigger or they look like they get bigger.

And you might have to use it on your nearbys.

You might want to move it out of the way, like you have a label underneath of a movie poster.

And you can constrain those using Auto Layouts.

So, you know, if I have this ImageView here, let's say that I have sized the ImageView to be the same size as the image.

Then you can constrain to the frame of the image.

But when it becomes enlarged, the frame doesn't change but the visible extent of the image does.

And so to find out how large the image will be when it becomes focused, we expose this layout guide called focusedFrameGuide.

And you can attach other constraints to that layout guide, and that way those views will be far enough away from the image that they won't be clipped by it when it becomes larger.

So let's run through this.

So I have my ImageView here, and I have this red outline that represents the frame of the UIImageView.

And when it becomes focused it's going to get bigger, but the frame is still at that red outline.

And so now this dotted outline shows where the focusedFrameGuide is.

And that focusedFrameGuide will always be there, even if it's not currently focused.

And so you can choose which one of these you want to attach your layout constraints to.

Or you may choose to change which constraints are active based on whether or not the element is currently focused.

And if you do that you should really use the coordination API that we expose on the focus update context to make sure that the animation happens in time with the system animation.

The system animation has a lot of subtlety in it in terms of what exactly the timing is.

It depends on how quickly the user is swiping or how far off-screen the view is focused at the moment it became focused.

So it's not really possible to get target values into your app.

You are going to need to use the coordination API to make sure that your animations will match the system animations.

And to show you how to do that, I'd like to invite Randy onstage to give you a demo.


[ Applause ]

Thank you Justin.

So today I am going to walk through those two common cases we have with adding interactivity to layered images.

And first we are going to look at a UIButton.

And second we are going to look at a custom collection view table cell.

So to get started, I have got a project here opened in Xcode with an asset catalog that has some layered images.

And I just have a ViewController subclass here.

And we are going to drag out a button into the canvas and inspect it to give it an image.

And we'll delete its title.

And then we'll add a couple of constraints to center it horizontally and vertically.

And then we'll update that Buttons frame to match those constraints.

You can do that with this menu down here, but I usually the keyboard shortcut Command Option Equals.

And if you look closely, there is a little bit of extra space around the button.

Those are the edge content insets.

We don't want those, so we can just delete them.

So that looks about like what we want.

So we'll build and run and see how it behaves.

Now I am using the tvOS simulator here, but I have paired a Siri remote with Mac so you can get a good feel for how the interaction is going to work.

And my finger is pressing down on the Select button, but it's just doing this darkening effect and sort of doing that sort of smooshing in.

And when I move my finger on the digitizer, it doesn't react to my touch.

So let's make that better.

To do that, I am going to open up the Assistant Editor for our ViewController subclass and drag out an outlet for the button.

And then in viewDidLoad, I am going to grab the ImageView from that button.

And I want to turn on that property that Justin told us about adjustsImage WhenAncestorFocused.

And now if I build and run, you should see that the button responds to my finger on the remote.

And it pushes in when I click on it.

But we still have an issue here because the edges of this button are clipped.

And that's because by default, the button's ImageView has clipsToBounds turned on.

So we just turn that off.

Build and run again.

We should get exactly what we are looking for.

It's a great interactive layered image that moves around with my finger and smooshes it when I press on it, great.

Let's do something a little more involved.

I have another project here, and this one has a CollectionViewController subclass in it.

And I have gone ahead and configured the sizing parameters of the CollectionView according to the Human Interface Guidelines for tvOS for a six-column layout.

Those guidelines tell us we want a cell width of 250 points.

And I know, based on my designs, I'll need 450 points vertically for the content of the cells.

And the guidelines tell us for a six-column layout we need 48 points between cells horizontally and at least 100 points between cells vertically.

And finally, to stay within the safe areas of a wide range of TVs, we want to leave 60 points at the top and the bottom and 90 points on the left and the right.

And those numbers are also in the guidelines.

So let's take a look at the cell itself.

I have already given it a custom class and a reuse identifier.

And we are going to zoom in a little bit and add some custom subviews.

First we'll drag out an ImageView.

And then we'll drag out a label.

Now I'll add some constraints to lay out these views.

We want the ImageView to hug the top left and right edges.

And we happen to know that all of our images have the same aspect ratio.

So I'll also add an aspect ratio constraint.

And then I'll edit that aspect ratio constraint to match our images so they have a ratio of 2-to-3.

And then I'll add some constraints to my label.

I'll Control-Drag from the label to the ImageView to add a horizontal centering constraint.

And then I also want my ImageView the top of my label will be 15 points from the bottom of my ImageView.

So I'll add a constraint between those things.

Now that we have added all those constraints, we can do Command Option Equals to get everything where it's supposed to be.

And finally, that font is a little small.

So I am going to bump it up to Body style, nice pick.

And we've got some outlets for the ImageView on our poster.

So we'll just hook those up and the label.

Now we'll take a quick look at our CollectionViewController.

You can see I have just implemented number of items in section, and cellForItemAt indexPath.

So if I build and run, we should get basically the layout we are looking for.

But we'll see what the behavior is like.

So this is pretty much the layout I was looking for.

It's all the right things.

But right away you can't tell which item is focused.

And when I click, you can't see which cell I am selecting.

So let's take care of all those things.

Now first, to see where the focus is we are going to go into Interface Builder and select our ImageView and check that adjustImagewhenfocused check mark.

And that'll be enough to get our cells to start responding to focus.

As you can see, it's big, and it reacts to my touch; and when I click it pushes in.

But the labels are overlapping on the bottom.

They are not responding to the change in the focus size.

So to do something about that, we are going to open up our custom cell class in the Assistant Editor.

And first we are going to drag out an outlet for that vertical constraint between the top of the label and the bottom of the ImageView.

So we'll add an outlet for that, and we'll call that the unfocusedConstraint.

And then I'll add another property for the focusedConstraint.

We'll set that focusedConstraint up in awakeFromNib to be the labels.topAnchor relative to the imageView's focusedFrameGuide bottomAnchor plus those 15 points.

Then in updateConstraints we'll set the focusedFrame we'll make the focusedConstraint active when the cell is focused.

And we'll make the unfocusedConstraint active in the opposite case.

Finally, in didUpdateFocus, we'll invalidate our constraints by calling setNeedsUpdateConstraints.

And then we'll add a coordinated animation by calling addCoordinatedAnimations.

And inside there, we'll call layoutIfNeeded.

And that'll be enough that now when we build and run, we should see the label has move down below out of the way of the focused poster.

And when I move from one to the other, they all animate out of the way and back to where they should be.


[ Applause ]

So remember, use focusedFrameGuide and coordinatedAnimations.

And don't forget to turn on adjustsImage WhenAncestorFocused to get these effects.

With that, back to Justin.

[ Applause ]

Thanks for that awesome demo, Randy.

So you can see how easy it is to really take advantage of this unique user interface element to really bring your tvOS apps to life.

All right, let's change gears a little bit now and talk about scrolling.

So scrolling on tvOS is a bit different than scrolling on iOS.

And it has to do with those indirect touches.

So when the user is scrolling in ScrollView on iOS, they are physically touching the ScrollView.

And as they move their finger, it will adjust the offset of the ScrollView to match the motion of their finger.

But that's not really how it works on tvOS.

On tvOS, you are not touching the screen, but you are also not manipulating the scroll views directly.

What you are really doing is you are changing what's the focused item.

Then as the focus item changes, the focus engine will detect that and it will automatically scroll any scroll views as needed to make sure that the focused view will always be onscreen.

The focus engine will choose the best offset that it can for you based on the size of the view and the size of the ScrollView.

But there are some times where you want to take control of what offset that it's going to send you to.

So for example, one common customization that people want to do is they want to that the focused view is always centered within the bounds of the ScrollView.

And you can do that.

And the way you can do that is with the UIScrollViewDelegate method that you might recognize from iOS which is scrollView WillEndDragging, withVelocity, targetContentOffset.

And the final parameter there, the targetContentOffset, is a pointer to a CGPoint.

And that point represents the offset that the focus engine will scroll you to if you don't do anything else.

That's the offset that it shows for you automatically.

But if you'd like to do your own calculations and decide for yourself what you want the scroll offset to be, you can do that.

Make your calculation about what offset you'd like, and then store the value into the point that's pointed to you by that pointer.

And then the focus engine will scroll there instead.

And you'll get all the great system animations that come with that, but to your selected offset.

But it's not to say that direct manipulation is always bad or is never a good idea on tvOS.

There are some cases where you'd want to do that.

And so some of the situations that call for that are things like you have a lot of full-screen content.

Maybe you have full-screen photo gallery, where the photos go completely to the edges of the screen.

So one example from UIKit is UIPageViewController.

When you have a UIPageViewController, it doesn't do the focused scrolling.

And instead, you are directly manipulating the ScrollView content of the PageViewController.

There are other cases where you have a lot of scrolling text.

So it's another good place where you might want to do direct manipulation.

Basically, anytime where it's not clear what ought to be the focused item, and you wouldn't be able to see what the next focused item would be, and those are good situations to do direct manipulation.

So if you want to do that, you don't have to write your own gesture handling code.

You can reuse the pan gesture that already comes on ScrollView.

The trick is that scroll views are configured to only listen for direct touches out of the box.

And the Siri remote is generating indirect touched.

So you just need to get this pan gesture recognizer and convince it to listen to indirect touches.

We also have a directional press gesture recognizer, which is only on ScrollView on tvOS.

And this is listening for those arrow button presses.

So that way a user who is not using a device that he can do touch input can also scroll ScrollViews.

And this gesture is disabled by default because we would rather have this arrow presses manipulate focus instead of manipulating the ScrollView offset.

So you'll have to enable it.

And this is the code that you can use to do both of those things.

So we are getting to panGestureRecognizer from the ScrollView.

And we are telling it that we'd like to listen for indirect touches.

And we are getting that directionalPressGesture Recognizer, and we are telling it that it should be enabled.

And if you do this, then all of those events that come into the ScrollView will directly manipulate the offset of the ScrollView.

And it's actually possible to combine this direct manipulation technique and the focus interaction technique.

And to show you how you can do that, I'd like to invite Kevin onstage to give you a demo.

Kevin [applause]?

Thanks Justin.

Hi. I'm Kevin Hiscott, and I'm an engineer on tvOS.

We are going to look at a sample view controller that presents terms and conditions text in a text view.

TextView is a subclass of ScrollView.

So we can take a look at how to add direct manipulation to it.

Let's head over to my ZIB [phonetic] and see what I have set up so far.

I have a text view with very lengthy lower mid sub [phonetic] pasted into it.

And if we go into our application, we can see what that looks like in the app context.

When I present my ViewController, we can see along the bottom edge that the text is actually clipped.

And if I try swiping the Siri remote, the text does not scroll.

Well let's go improve that.

Let's head over to my ViewController.

And in viewDidLoad we are going to grab the outlet I have created to that UITextView.

I have called it messageTextView.

And on messageTextView and any ScrollView, there is a panGestureRecognizer.

And we are going to change its allowed touch types to include the indirect touch type.

As Justin mentioned, those are the touches created by the Siri remote.

The second thing we are going to do is set isSelectable to true.

This will allow the text view to be focused, to become focused, and receive these events.

Let's build and run and see how this works.

Now when I present my ViewController, I am actually able to directly swipe to scroll the text up and down with the Siri remote.

Great direct manipulation.

The other thing any good terms and conditions ViewController needs is a way for the user to either accept or reject your conditions.

So to do that, let's head over to my ZIB and drag in a couple of buttons.

I am going to label one Disagree and the other one Agree.

Let's build and run and see what happens.

What I hope to happen is for our focus to move horizontally with horizontal swipes, and for vertical swipes to still scroll the text view.

However, when I present my ViewController I can see that vertical swipes does move the text; however, if I swipe too far down, focus actually moves down into the buttons, and I can no longer scroll my text until focus moves back into the text view.

This isn't exactly the behavior I was looking for.

Let's head back to my ViewController.

And we are going to disable isSelectable.

We don't actually want the TextView to be a focusable element onscreen.

Instead, I am going to show you a little trick.

We are going to grab the panGestureRecognizer and actually add it to our ViewController's view instead.

This will allow the message TextView to receive events without being focusable itself.

Let's build and run and see how it behaves.

Now when I present my ViewController, we can see that the Disagree button immediately becomes focused.

And that's because the text view is no longer focusable.

I can swipe the Siri remote left and right to move focus.

But any time I can swipe up and down to move the text.

Great this is looking really good.

The last thing we need to consider on tvOS are directional press type events generated by other input methods like game controllers, keyboards, or IR remotes.

And we make this really easy to support on tvOS.

Let's head back to my view controller, and we are going to grab the directionalPressGesture Recognizer from our messageTextView and enable it.

This will allow scrolling to occur with those press types generated on those other devices.

The last thing we are going to do is move that GestureRecognizer onto our own view, just as we did before.

Let's build and run and see how this all comes together.

Now when I present my terms and conditions, I can both swipe left and right to move focus, but I can also tap left and right to move focus with the directional press type events generated on the Siri remote and all the other devices.

I can swipe up and down to change the content offset, but I can also tap up and down to incrementally scroll.

Great! This is a fully functional terms and conditions ViewController for tvOS.

Thanks so much for your time.

And I can't wait to accept all the terms and conditions in your apps.

Back to Justin [applause].

All right, let's move on to our final topic, which is text input.

So we have this great system keyboard on tvOS.

And there is a lot of features that are only available if you are using the system keyboard.

I want to go through just a few of them for you.

One of them is dictation.

So the user can just speak into the Siri remote and their words will show up on the screen.

We also support Bluetooth keyboards.

So if you pair it with a Bluetooth keyboard, and you just type right on there.

We have the new Apple TV Remote app so that users can pair their phone with their TV and they can type right on their phone's keyboard to send text to the TV.

There is also a lot of localization details the system keyboard takes care of for you.

And it also automatically changes its layout based on the input device the user is using.

So there is the linear layout that you are probably most familiar with, but there is also a grid layout that we'll use if there is no Siri remote paired with the TV.

These are a lot of great features that are only available in the system keyboard.

And if you are not using the system keyboard, then users won't be able to take advantage of these features.

So we discourage you from trying to roll your own keyboard.

Really stick to the system keyboard so that users can enjoy all these great features that are only available through the system one.

Now there are some ways that you can customer the built-in system keyboard.

And one way that you can customize it is to add some of your own views to the keyboard that will appear alongside of it.

And you can use some API that's on UIResponder that you might recognize from iOS.

There is the inputAccessoryView and the inputAccessory ViewController.

And if you assign to these properties on the text fields that you are wanting to edit text in, then when the keyboard appears you can show some of your own views right there alongside the keyboard.

And I'll show you a screenshot of that here in just a second.

And finally, I want to point out one change in behavior that's happened between tvOS 9 and tvOS 10.

UITextField has this property called keyboardAppearance, and you can set to unspecified light or dark.

And on tvOS 9, if you set it, say, to dark, then not only would the keyboard itself be dark when it presents itself, but the text field would also be dark as it's sitting there on the screen.

Well now in tvOS 10 we have this new appearance API.

And so the entire system can respond to light or to dark.

And so now what we do on tvOS 10 is that this keyboard appearance property will only affect the keyboard, and the appearance of the text field will now be dictated by the larger tray collection in UPI.

Okay, so here's the screen shot I promised you.

So in this case I have created just a regular text field, and I gave it an inputAccessoryView.

And I put a red border on it so you can see where it is.

And those UI labels in there are there are totally custom.

And I just put them in there so you can add any kind of accessory views that you'd like that could appear right above the keyboard.

Now another great way that you can get UI on the screen at the same time as the keyboard is to use SearchController.

And you use some APIs you also might recognize from iOS, and that's UISearchController.

So we have the keyboard, and the search results appear below it.

And this also automatically adapts.

There is a grid-based layout and a linear-based layout depending on the input device the user is using.

And you can even embed this inside of other view controllers.

SearchController usually wants to be presented modally and take over the whole screen.

But you can embed it inside of another search another view controller by using UISearchContainer ViewController.

And I'll show you a code sample of that, just a few slides.

You provide your own custom view controller for search results.

So the visual appearance of the search results and the interaction of them is completely up to you.

You can do whatever you want.

You might use a collection view, or maybe you'll use a split view that has a table view and a collection view if you want.

Any other things you'd like it's totally up to you for that view controller.

So here's a quick sample of how you might embed a search controller inside of something else like, say, a tab bar controller.

So I have got my custom ViewController class here.

And in viewDidAppear I am going to create a searchController.

I want to make sure I don't do this twice, so I am checking to make sure I haven't done this already.

Then I am creating my UISearchController and telling it view controller to use for my results and who to contact as the search query changes.

Then I am going to wrap that inside of this UISearchContainer ViewController.

And this allows me now to take this container, and I can do regular ViewController containment on it.

And then I am going to add it as a ChildViewController.

And if you do something like this, then you'll be able to easily use a search controller inside of a tab bar, much like some of the system apps on tvOS.

And that is text input.

So in summary, one of the things that we want you to take away from this talk.

First one is, when you are doing Menu button handling, be careful.

Keep in mind that you don't want to interfere with the system gestures.

When you are working with layered images, take advantage of the layout guides that we have given you and the coordinated animation API to make sure that your views can move around in tandem with the system animations.

If you want to do direct manipulation on ScrollViews you don't have to do any of that handling code yourself.

Just reuse the pan gestures that already come built in.

And finally, take advantage of the system keyboard to make sure that your users are getting all the features they can when it comes to text input.

So for more information about this talk, you can visit this URL, where you'll find links to documentation.

We have got a bunch of other sessions that are coming up this week.

I want to call up two of them in particular that are both about UIKit on tvOS.

So have a great rest of the conference.

Thank you.

[ Applause ]

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US