10 Jul 2024
Working with prebuilt SwiftUI element like button and image in tvOS is easy but when it comes to customization, it can be a bit tricky. In this article, we will focus on how to customize the focus on custom UI elements in tvOS.
Please note that order of modifiers used is very important while customizing the focus on custom UI elements. If you messup the order of the modifiers, the focus might not work as expected.
I will be using the following code snippet to demonstrate the focus on custom UI elements in tvOS.
// 1. Custom hashable noded that can be used as focus state
struct Person: Hashable {
var name: String
var address: String
static func all() -> [Person] {
return [
Person(name: "Mr X", address: "Japan"),
Person(name: "Miss Y", address: "United States"),
Person(name: "Mrs Z", address: "United Kingdom"),
]
}
}
struct ContentView: View {
// 2. Focus State
@FocusState var person: Person?
var body: some View {
List {
// 3. Displays the name of person that is focused
Text(person?.name ?? "No focus")
ForEach(Person.all(), id: \.name) { person in
VStack {
ListItem(person: person)
}
// 4. VStack is important to wrap ListItem so that isFocused environment variable is triggered when ListItem is triggered.
.focusable()
// 5. After view is made focusable then you can use .focused view modifier on it to bind with focus state
.focused($person, equals: person)
// 6. On tap gesture is added only after focusable view modifier is added
.onTapGesture {
print("Tapp")
}
}
}
.listStyle(.plain)
.frame(maxWidth: 400)
}
}
struct ListItem: View {
// 7. Returns whether the nearest focusable ancestor has focus.
@Environment(\.isFocused) var focused
var person: Person
var body: some View {
Text(person.name)
.foregroundColor(focused ? .white : .black)
.padding()
.background(focused ? .orange : .gray)
.cornerRadius(20)
.scaleEffect(focused ? 1.2 : 1.0)
}
}
Important bits of code
- Custom hashable node that can be used as focus state
- Focus State
- Displays the name of the person that is focused
- VStack is important to wrap ListItem so that the isFocused environment variable is triggered when ListItem is triggered.
- After the view is made focusable, you can use the .focused view modifier on it to bind with the focus state
- On tap gesture is added only after the focusable view modifier is added
- Returns whether the nearest focusable ancestor has focus.
Output
