Tvos Button Inside Navigationlink Is Not Working

NavigationLink buttons on tvOS with SwiftUI not working

NavigationLink works by itself, standalone, only on watchOS (that might confuse), in all other supported OSs it should be included in NavigationView to operate, so

in pseudo-code

  NavigationView {
// ... some code

NavigationLink(...) // must be anywhere inside

// ... other code
}

SwiftUI Button tvOS+iOS action working for iOS not on tvOS

The problem in your code is that your focusable modifier is blocking the clicks.

To solve this you can reimplement the button from the scratch. I've created CustomButton inspired by this answer: it'll be a plain button on iOS and a custom one on Apple TV:

struct CustomButton<Content>: View where Content : View {
@State
private var focused = false
@State
private var pressed = false

let action: () -> Void
@ViewBuilder
let content: () -> Content

var body: some View {
contentView
.background(focused ? Color.green : .yellow)
.cornerRadius(20)
.scaleEffect(pressed ? 1.1 : 1)
.animation(.default, value: pressed)
}

var contentView: some View {
#if os(tvOS)
ZStack {
ClickableHack(focused: $focused, pressed: $pressed, action: action)
content()
.padding()
.layoutPriority(1)
}
#else
Button(action: action, label: content)
#endif
}
}

class ClickableHackView: UIView {
weak var delegate: ClickableHackDelegate?

override init(frame: CGRect) {
super.init(frame: frame)
}

override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
if validatePress(event: event) {
delegate?.pressesBegan()
} else {
super.pressesBegan(presses, with: event)
}
}

override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
if validatePress(event: event) {
delegate?.pressesEnded()
} else {
super.pressesEnded(presses, with: event)
}
}

override func pressesCancelled(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
if validatePress(event: event) {
delegate?.pressesEnded()
} else {
super.pressesCancelled(presses, with: event)
}
}

private func validatePress(event: UIPressesEvent?) -> Bool {
event?.allPresses.map({ $0.type }).contains(.select) ?? false
}

override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
delegate?.focus(focused: isFocused)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override var canBecomeFocused: Bool {
return true
}
}

protocol ClickableHackDelegate: AnyObject {
func focus(focused: Bool)
func pressesBegan()
func pressesEnded()
}

struct ClickableHack: UIViewRepresentable {
@Binding var focused: Bool
@Binding var pressed: Bool
let action: () -> Void

func makeUIView(context: UIViewRepresentableContext<ClickableHack>) -> UIView {
let clickableView = ClickableHackView()
clickableView.delegate = context.coordinator
return clickableView
}

func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<ClickableHack>) {
}

func makeCoordinator() -> Coordinator {
return Coordinator(self)
}

class Coordinator: NSObject, ClickableHackDelegate {
private let control: ClickableHack

init(_ control: ClickableHack) {
self.control = control
super.init()
}

func focus(focused: Bool) {
control.focused = focused
}

func pressesBegan() {
control.pressed = true
}

func pressesEnded() {
control.pressed = false
control.action()
}
}
}

Usage:

CustomButton(action: {
print("clicked 1")
}) {
Text("Clickable 1")
}
CustomButton(action: {
print("clicked 2")
}) {
Text("Clickable 2")
}

Result:

Sample Image

onFocusChange not triggered in SwiftUI tvOS

The goal the question is unclear, but here is a simple demo of alternate approach to have managed focused & button click using custom button style. Maybe this will be helpful.

Tested with Xcode 12 / tvOS 14 (Simulator) - compare regular button vs custom button

Sample Image

struct MyButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 1.2 : 1)
}
}

struct ContentView: View {
@State private var focused = false
var body: some View {
VStack {
Button(action: {
print(">>>> custom button")
}) { LabelView() }.buttonStyle(MyButtonStyle())

Button("Regular Button") {
print(">> regular button")
}
}
}
}

struct LabelView: View {
@Environment(\.isFocused) var focused: Bool
var body: some View {
RoundedRectangle(cornerRadius: 25.0)
.frame(width: 200, height: 100)
.foregroundColor(focused ? .blue : .gray)
.overlay(Text("Title").foregroundColor(.white))
}
}


Related Topics



Leave a reply



Submit