SwiftUI on tvOS

Build your own tvOS app while brushing up your SwiftUI skills. Get hands-on practice with tvOS lazy views and the Focus Engine. By Jordan Osterberg.

Leave a rating/review
Download materials
Save for later
Share

tvOS is an exciting, but mostly unexplored, platform in the Apple ecosystem for outside developers. In this tutorial, you’ll explore this platform using the power of SwiftUI.

Along the way, you’ll:

  • Make your first tvOS app with SwiftUI
  • Get reintroduced to foundational SwiftUI building blocks
  • Utilize lazy views, introduced in tvOS 14
  • Tell the Focus Engine what to focus on
  • Reuse views in your app
  • Play a local video file using SwiftUI

You’ll do this by working on a tvOS app named RickTV. The app displays a wide variety of content, only to Rickroll you regardless of what you wanted to watch. :]

Getting Started

Download the project materials using the Download Materials button at the top or bottom of this tutorial.

Open the project in Xcode and select any of the tvOS simulators, then build and run.

A thumbnail listing all videos

The starter project contains a list of video titles, which you can select to view their details. There are three tabs:

  • All Videos: This displays all the videos offered by the app.
  • Favorites: All the videos you mark as favorites go here.
  • Lots of Videos: A massive amount of videos, designed to be as intensive as possible for tvOS and SwiftUI to handle. You’ll learn more about this tab in the section about lazy views.

An important thing to note is that, unlike other simulators, you must use a Siri Remote to interact with the tvOS simulator. In the Simulator menu, select Window ▸ Show Apple TV remote. On the Siri Remote, hold Option and use your system’s pointing device to scroll in the simulator.

Reviewing SwiftUI

With tvOS 14, the SwiftUI app lifecycle changed. In prior versions, you had to rely on UIKit’s AppDelegate system to manage the app’s lifecycle. Fortunately, Apple fixed this and introduced new APIs: App, Scene and @main.

You can define an app in SwiftUI with only a few lines of code. To see this, open the starter project, then open RickTVApp.swift:

// 1
@main
// 2
struct RickTVApp: App {
  // 3
  var body: some Scene {
    // 4
    WindowGroup {
      ContentView()
    }
  }
}

Here’s what’s happening in the code above. This code tells SwiftUI about your app:

  1. @main is how SwiftUI knows where to find your app. There can only be one @main label in an app.
  2. You create a new struct, RickTVApp, which subclasses App. All SwiftUI apps that use the SwiftUI lifecycle must conform App.
  3. RickTVApp has a property named body, which is where SwiftUI looks to find your app’s content. This body type can be any Scene type. In this app, it’s WindowGroup. body isn’t unique to App. All views have a body as well.
  4. Finally, inside of WindowGroup‘s body, you return a new ContentView, which contains your top-level view.

Open ContentView.swift.

At the top, you’ll notice a property named dataProvider:

@ObservedObject var dataProvider = DataProvider()

DataProvider is a custom class that implements ObservableObject. This allows your view to listen for any changes to certain properties within that class — those marked with @Published.

This is a foundational principle of SwiftUI: You can observe objects or properties and your views will update automatically when they change. @ObservedObject is one of the ways to listen for changes in your app’s data.

If you don’t want or need a full class to listen for changes — for example, you have a piece of data that is relevant only to your view — create a @State property. For example:

@State var timesPressed = 0

You could use this property, marked with @State, to track the number of button presses. From there, you could show this number as text within your app:

Text("\(timesPressed)")

Because you marked timesPressed with @State, your Text view will update whenever timesPressed changes.

Adding a Thumbnail Preview

To complete your starter project, you’ll add a thumbnail preview of the video along with a description. This is how the design will look when you’ve finished:

A single video's thumbnail, title and description

Open VideoThumbnailView.swift. Inside, you’ll see a VStack, with a Text view nested in it.

A VStack, or vertical stack, is a collection of views that display in a vertical layout. There’s also an HStack for horizontal layout.

Replace the Text view inside the VStack with an Image:

Image(video.thumbnailName)

Build and run.

A video thumbnail on its own

It’s a great start. Next, you’ll make the image look more like a traditional video app’s thumbnail image. Replace the recently added Image with the following:

Image(video.thumbnailName)
  // 1
  .resizable()
  // 2
  .renderingMode(.original)
  // 3
  .aspectRatio(contentMode: .fill)
  // 4
  .frame(width: 450, height: 255)

These modifiers on the Image view allow you to change the behavior of a view. In the code above, you:

  1. Make the image resizable.
  2. Change the rendering mode to .original, which tells SwiftUI to use the image in its original format.
  3. Change the content mode of the image to .fill, which means the image will expand to fill the frame of the view instead of being squished.
  4. Set the width and height of the image to a predetermined size.

Next, add the following three modifiers at the end of the list:

// 1
.clipped()
// 2
.cornerRadius(10)
// 3
.shadow(radius: 5)

Here, you:

  1. Use clipped() to ensure the image doesn’t go beyond the frame of the view.
  2. Set the corner radius to 10 so there aren’t any sharp edges on the image.
  3. Add a nice drop shadow for some extra depth.

Finally, add the two Text views inside another VStack to show the title and description. Place these after .shadow(radius: 5):

// 1
VStack(alignment: .leading) {
  // 2
  Text(video.title)
    .foregroundColor(.primary)
    .font(.headline)
    .bold()
  // 3
  Text(video.description.isEmpty ? 
    "No description provided for this video." : 
    video.description)
    .font(.subheadline)
    .foregroundColor(.secondary)
    .multilineTextAlignment(.leading)
    .lineLimit(2)
    .frame(height: 80)
}

Here’s what’s going on in the code above:

  1. Create a new vertical stack.
  2. Add a new Text to display the video title with modifiers to update aspects of the text such as color and font.
  3. Add another Text to display the video description with modifiers to update the appearance and to add limits to the number of lines and the height.

Build and run.

A list of video thumbnails, titles, and descriptions

Voilà! The app looks so much better now. :]