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
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))
}
}
Getting onFocusChange callback for Buttons in SwiftUI (tvOS)
Try using @Environment(\.isFocused)
and .onChange(of:perform:)
in a ButtonStyle:
struct ContentView: View {
var body: some View {
Button("top") {
// button action
}
.buttonStyle(MyButtonStyle())
}
}
struct MyButtonStyle: ButtonStyle {
@Environment(\.isFocused) var focused: Bool
func makeBody(configuration: Configuration) -> some View {
configuration.label
.onChange(of: focused) { newValue in
// do whatever based on focus
}
}
}
IIRC using @Environment(\.isFocused)
inside a ButtonStyle may only work on iOS 14.5+, but you could create a custom View instead of a ButtonStyle to support older versions.
SwiftUI Share Button between iOS and TVOS, focusable(_:onFocusChange:)' is unavailable in iOS
You need to use conditional compilation for such cases. For better reusability to is possible to wrap it in view modifier, like
struct DemoFocusableModifier: ViewModifier {
private let isFocusable: Bool
private let onFocusChange: (Bool) -> Void
init (_ isFocusable: Bool = true, onFocusChange: @escaping (Bool) -> Void = { _ in }) {
self.isFocusable = isFocusable
self.onFocusChange = onFocusChange
}
@ViewBuilder
func body(content: Content) -> some View {
#if os(tvOS)
content
.focusable(isFocusable, onFocusChange: onFocusChange)
#else
content
#endif
}
}
and use (instead of your .focusable
) as
}
.modifier(DemoFocusableModifier { focused in
// content here
})
Prepared with Xcode 13 / iOS 15
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:
Related Topics
Cannot Create PDF Document with 400+ Pages on iOS
How to Show "Would Like to Send You Push Notifications" Alert View Again
Avassetwriter Avvideoexpectedsourceframeratekey (Frame Rate) Ignored
API Facebook iPhone , Possible to Post to a Friend's Wall
How to Only Override a Method Depending on the Runtime System iOS Version
Thread 1:Exc_Bad_Access (Code = 1, Address = 0X30000008)
How to Properly Remove Node When Out of Screen Bounds
Delphi Xe4 iOS Can't Connect to Paserver
iOS 7 Uitoolbar Overriding with Status Bar
Generate Rsa Public Key from Modulus and Exponent
How to Fix Status Bar Overlap Issue in iOS 7
iOS Custom Annotation: a View Below the Annotation Pin
Itms-90809: Deprecated API Usage - Existing App That Use Uiwebview Are No Longer Accepted
Are Storyboards Going to Work on iOS 4