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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
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.
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:
-
@main
is how SwiftUI knows where to find your app. There can only be one@main
label in an app. - You create a new struct,
RickTVApp
, which subclassesApp
. All SwiftUI apps that use the SwiftUI lifecycle must conformApp
. -
RickTVApp
has a property namedbody
, which is where SwiftUI looks to find your app’s content. Thisbody
type can be anyScene
type. In this app, it’sWindowGroup
.body
isn’t unique toApp
. All views have abody
as well. - Finally, inside of
WindowGroup
‘s body, you return a newContentView
, 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:
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.
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:
- Make the image resizable.
- Change the rendering mode to
.original
, which tells SwiftUI to use the image in its original format. - 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. - 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:
- Use
clipped()
to ensure the image doesn’t go beyond the frame of the view. - Set the corner radius to 10 so there aren’t any sharp edges on the image.
- 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:
- Create a new vertical stack.
- Add a new
Text
to display the video title with modifiers to update aspects of the text such as color and font. - 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.
Voilà! The app looks so much better now. :]