SwiftUI Button tvOS+iOS 操作适用于 iOS 不适用于 tvOS
SwiftUI Button tvOS+iOS action working for iOS not on tvOS
我正在尝试为 iOS 和 tvOS 共享一个 swiftUI 按钮视图。 iOS 一切正常,但 tvOS 未触发按钮操作。
var body: some View {
Button(action: tapEvent) {
HStack {
if let image = icon, let uiimage = UIImage(named: image) {
Image(uiImage: uiimage)
.resizable()
.renderingMode(.template)
.foregroundColor(iconColorForState(active: active, buttonMode: mode))
.frame(width: 20, height: 20, alignment: .center)
}
if let title = label {
Text(title)
.lineLimit(1)
}
}
}
.buttonStyle(PlainButtonStyle())
.foregroundColor(foregroundColorForState(active: active, buttonMode: mode))
.padding()
.disabled(disabled)
.background(self.focused ? focussedBackgroundColorFor(active: active, mode: mode) : backgroundColorFor(active: active, mode: mode))
.frame(height: heightForButtonSize(size: size ?? .Base))
.cornerRadius(8)
.modifier(FocusableModifier { focused in
self.focused = focused
})
}
FocusableModifier(允许在 iOS 和 tvOS 中构建按钮 class):
struct FocusableModifier: 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
}
}
然后我在 iOS 和 tvOs 视图中像这样使用这个按钮:
ButtonView(label: "Primary", mode: .Primary, tapEvent: { print("Tapped") })
问题是在 iOS 上按钮动作在点击时执行,但在 tvOS 上按钮点击事件不执行。
更新:
我发现当删除 cornerRadius 和修改器时,按钮事件确实被触发了。如果按钮上存在 cornerRadius 或修饰符,tvOS 不想再响应按钮点击。我仍然需要修饰符和 cornerRadius。
您的代码中的问题是您的 focusable
修饰符阻止了点击。
要解决这个问题,您可以从头开始重新实现按钮。我受 启发创建了 CustomButton
:它在 iOS 上是一个普通按钮,在 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({ [=10=].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()
}
}
}
用法:
CustomButton(action: {
print("clicked 1")
}) {
Text("Clickable 1")
}
CustomButton(action: {
print("clicked 2")
}) {
Text("Clickable 2")
}
结果:
我正在尝试为 iOS 和 tvOS 共享一个 swiftUI 按钮视图。 iOS 一切正常,但 tvOS 未触发按钮操作。
var body: some View {
Button(action: tapEvent) {
HStack {
if let image = icon, let uiimage = UIImage(named: image) {
Image(uiImage: uiimage)
.resizable()
.renderingMode(.template)
.foregroundColor(iconColorForState(active: active, buttonMode: mode))
.frame(width: 20, height: 20, alignment: .center)
}
if let title = label {
Text(title)
.lineLimit(1)
}
}
}
.buttonStyle(PlainButtonStyle())
.foregroundColor(foregroundColorForState(active: active, buttonMode: mode))
.padding()
.disabled(disabled)
.background(self.focused ? focussedBackgroundColorFor(active: active, mode: mode) : backgroundColorFor(active: active, mode: mode))
.frame(height: heightForButtonSize(size: size ?? .Base))
.cornerRadius(8)
.modifier(FocusableModifier { focused in
self.focused = focused
})
}
FocusableModifier(允许在 iOS 和 tvOS 中构建按钮 class):
struct FocusableModifier: 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
}
}
然后我在 iOS 和 tvOs 视图中像这样使用这个按钮:
ButtonView(label: "Primary", mode: .Primary, tapEvent: { print("Tapped") })
问题是在 iOS 上按钮动作在点击时执行,但在 tvOS 上按钮点击事件不执行。
更新: 我发现当删除 cornerRadius 和修改器时,按钮事件确实被触发了。如果按钮上存在 cornerRadius 或修饰符,tvOS 不想再响应按钮点击。我仍然需要修饰符和 cornerRadius。
您的代码中的问题是您的 focusable
修饰符阻止了点击。
要解决这个问题,您可以从头开始重新实现按钮。我受 CustomButton
:它在 iOS 上是一个普通按钮,在 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({ [=10=].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()
}
}
}
用法:
CustomButton(action: {
print("clicked 1")
}) {
Text("Clickable 1")
}
CustomButton(action: {
print("clicked 2")
}) {
Text("Clickable 2")
}
结果: