如何在 SwiftUI tvOS 中检测“点击”手势

Ahm*_*raz 7 swift tvos swiftui

使用:

  • 用户界面
  • 斯威夫特 5
  • 操作系统
  • Xcode 版本 11.2.1

我只想检测下面 URLImage 上的点击手势

JFYI我对 Xcode、Swift 和 SwiftUI 非常陌生(不到 3 周)。

URLImage(URL(string: channel.thumbnail)!,
                 delay: 0.25,
                 processors: [ Resize(size: CGSize(width:isFocused ?  300.0 : 225.0, height:isFocused ?  300.0 : 225.0), scale: UIScreen.main.scale) ],
                 content:  {
                    $0.image
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .clipped()
        })
            .frame(width: isFocused ?  300.0 : 250.0, height:isFocused ?  300.0 : 250.0)
            .clipShape(Circle())
            .overlay(
                Circle().stroke( isFocused ? Color.white : Color.black, lineWidth: 8))
            .shadow(radius:5)
            .focusable(true, onFocusChange:{ (isFocused) in
                withAnimation(.easeInOut(duration:0.3)){
                    self.isFocused = isFocused
                }
                if(isFocused){
                    self.manager.bannerChannel = self.channel
                    print(self.manager.bannerChannel)
                    self.manager.loadchannelEPG(id: self.channel.id)
                }
            })
            .padding(20)
    }
Run Code Online (Sandbox Code Playgroud)
  • 我发现的唯一解决方法是将其包装在 NavigationLink 或 Button 中,但随后无法将焦点放在按钮上。
  • 我发现如果我向 Button/NavigationLink 添加圆角半径,则它会在 Button/NavigationLink 上运行,但默认单击操作不会运行
  • 此外,TapGesture 在 tvOS 中不可用

由于手势可用,也许有一种我无法弄清楚的使用手势的方法。

或者

如果有一种方法可以将焦点放在按钮上(尽管这是不太受欢迎的选择,因为这会改变我想要实现的外观)。

Jef*_*son 12

这是可能的,但不适合胆小的人。我想出了一个可能对您有所帮助的通用解决方案。我希望在下一个 swiftUI 更新中,Apple 添加了一种更好的方式来为 tvOS 附加点击事件,并且可以将此代码归入它所属的垃圾箱。

如何做到这一点的高级解释是制作一个捕获焦点和点击事件的 UIView,然后制作一个 UIViewRepresentable 以便 swiftUI 可以使用该视图。然后将视图添加到 ZStack 中的布局中,因此它是隐藏的,但您可以接收焦点并响应单击事件,就好像用户真的在与您真正的 swiftUI 组件交互一样。

首先,我需要制作一个捕获事件的 UIView。

class ClickableHackView: UIView {
    weak var delegate: ClickableHackDelegate?
    
    override init(frame: CGRect) {
        super.init(frame: frame)        
    }

    override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
        if event?.allPresses.map({ $0.type }).contains(.select) ?? false {
            delegate?.clicked()
        } else {
            superview?.pressesEnded(presses, with: event)
        }
    }

    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
    }
}
Run Code Online (Sandbox Code Playgroud)

~ 可点击的委托:

protocol ClickableHackDelegate: class {
    func focus(focused: Bool)
    func clicked()
}
Run Code Online (Sandbox Code Playgroud)

然后为我的视图做一个 swiftui 扩展

struct ClickableHack: UIViewRepresentable {
    @Binding var focused: Bool
    let onClick: () -> 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 clicked() {
            control.onClick()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我制作了一个更友好的 swiftui 包装器,这样我就可以传入任何我想要可聚焦和可点击的组件

struct Clickable<Content>: View where Content : View {
    let focused: Binding<Bool>
    let content: () -> Content
    let onClick: () -> Void
    
    @inlinable public init(focused: Binding<Bool>, onClick: @escaping () -> Void, @ViewBuilder content: @escaping () -> Content) {
        self.content = content
        self.focused = focused
        self.onClick = onClick
    }
    
    var body: some View {
        ZStack {
            ClickableHack(focused: focused, onClick: onClick)
            content()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

struct ClickableTest: View {
    @State var focused1: Bool = false
    @State var focused2: Bool = false
    
    var body: some View {
        HStack {
            Clickable(focused: self.$focused1, onClick: {
                print("clicked 1")
            }) {
                Text("Clickable 1")
                    .foregroundColor(self.focused1 ? Color.red : Color.black)
            }
            Clickable(focused: self.$focused2, onClick: {
                print("clicked 2")
            }) {
                Text("Clickable 2")
                    .foregroundColor(self.focused2 ? Color.red : Color.black)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Naren您可以添加带有 isActive 状态条件的隐藏 NavigationLink。然后在可点击处理程序内将条件设置为 true。NavigationLink(目的地: Connected(), isActive: self.$connected) { EmptyView() }.hidden() (2认同)

小智 11

如果您想避免,您可以通过设置非常短的按压持续时间UIKit来实现所需的解决方案。Long Press Gesture

1. 仅Press

如果你只需要处理按压动作,根本不需要长按。

ContentView()
   .onLongPressGesture(minimumDuration: 0.01, pressing: { _ in }) {
       print("pressed")
   }
Run Code Online (Sandbox Code Playgroud)

2PressLong press

如果您需要同时处理按下和长按。

var longPress: some Gesture {
    LongPressGesture(minimumDuration: 0.5)
       .onEnded { _ in
          print("longpress")
       }
}

ContentView()
   .highPriorityGesture(longPress)
   .onLongPressGesture(minimumDuration: 0.01, pressing: { _ in }) {
       print("press")
   }
Run Code Online (Sandbox Code Playgroud)