How do you detect a SwiftUI touchDown event with no movement or duration?

ぐ巨炮叔叔 提交于 2020-06-12 06:02:07

问题


I'm trying to detect when a finger first makes contact with a view in SwiftUI. I could do this very easily with UIKit Events but can't figure this out in SwiftUI.

I've tried a DragGesture with minimum movement of 0 but it still won't change until your finger moves.

TapGesture will only work when you lift your finger and LongPressGesture will not trigger fast enough no matter what I set the parameters to.

DragGesture(minimumDistance: 0, coordinateSpace: .local).onChanged({ _ in print("down")})

LongPressGesture(minimumDuration: 0.01, maximumDistance: 100).onEnded({_ in print("down")})

I want to detect a touchDown event as soon as a finger makes contact with a view. Apple's default gestures have restrictions to either distance or time.


回答1:


If you combine the code from these two questions:

How to detect a tap gesture location in SwiftUI?

UITapGestureRecognizer - make it work on touch down, not touch up?

You can make something like this:

ZStack {
    Text("Test")
    TapView {
        print("Tapped")
    }
}
struct TapView: UIViewRepresentable {
    var tappedCallback: (() -> Void)

    func makeUIView(context: UIViewRepresentableContext<TapView>) -> TapView.UIViewType {
        let v = UIView(frame: .zero)
        let gesture = SingleTouchDownGestureRecognizer(target: context.coordinator,
                                                       action: #selector(Coordinator.tapped))
        v.addGestureRecognizer(gesture)
        return v
    }

    class Coordinator: NSObject {
        var tappedCallback: (() -> Void)

        init(tappedCallback: @escaping (() -> Void)) {
            self.tappedCallback = tappedCallback
        }

        @objc func tapped(gesture:UITapGestureRecognizer) {
            self.tappedCallback()
        }
    }

    func makeCoordinator() -> TapView.Coordinator {
        return Coordinator(tappedCallback:self.tappedCallback)
    }

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

class SingleTouchDownGestureRecognizer: UIGestureRecognizer {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        if self.state == .possible {
            self.state = .recognized
        }
    }
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        self.state = .failed
    }
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        self.state = .failed
    }
}

There's definitely some abstractions we can make so that the usage is more like the other SwiftUI Gestures, but this is a start. Hopefully Apple builds in support for this at some point.




回答2:


You can use the .updating modifier like this:

struct TapTestView: View {

    @GestureState private var isTapped = false

    var body: some View {

        let tap = DragGesture(minimumDistance: 0)
            .updating($isTapped) { (_, isTapped, _) in
                isTapped = true
            }

        return Text("Tap me!")
            .foregroundColor(isTapped ? .red: .black)
            .gesture(tap)
    }
}

Some notes:

  • The zero minimum distance makes sure the gesture is immediately recognised
  • The @GestureState property wrapper automatically resets its value to the original value when the gesture ends. This way you only have to worry about setting isTapped to true. It will automatically be false again when the interaction ends.
  • The updating modifier has this weird closure with three parameters. In this case we are only interested in the middle one. It's an inout parameter to the wrapped value of the GestureState, so we can set it here. The first parameter has the current value of the gesture; the third one is a Transaction containing some animation context.



回答3:


This is a solution to detect changes between states and also the coordinates of the touch (within the Text View in this case):

I added an enum to manage the states (using began, moved and ended for those UIKit-nostalgic)

import SwiftUI

struct ContentView: View {
    @State var touchPoint = CGPoint(x: 0, y: 0)
    @State var touchState = TouchState.none
    var body: some View {
        Text("\(touchState.name): \(Int(self.touchPoint.x)), \(Int(self.touchPoint.y))")
            .border(Color.red).font(.largeTitle)
            .gesture(
                DragGesture(minimumDistance: 0)
                    .onChanged({ (touch) in
                        self.touchState = (self.touchState == .none || self.touchState == .ended) ? .began : .moved
                        self.touchPoint = touch.location
                    })
                    .onEnded({ (touch) in
                        self.touchPoint = touch.location
                        self.touchState = .ended
                    })
            )
    }
}
enum TouchState {
    case none, began, moved, ended
    var name: String {
        return "\(self)"
    }
}



回答4:


You can create a view modifier this way:

extension View {
    func onTouchDownGesture(callback: @escaping () -> Void) -> some View {
        modifier(OnTouchDownGestureModifier(callback: callback))
    }
}

private struct OnTouchDownGestureModifier: ViewModifier {
    @State private var tapped = false
    let callback: () -> Void

    func body(content: Content) -> some View {
        content
            .simultaneousGesture(DragGesture(minimumDistance: 0)
                .onChanged { _ in
                    if !self.tapped {
                        self.tapped = true
                        self.callback()
                    }
                }
                .onEnded { _ in
                    self.tapped = false
                })
    }
}

Now you can use it like:

struct MyView: View {
    var body: some View {
        Text("Hello World")
            .onTouchDownGesture {
                print("View did tap!")
            }
    }
}



回答5:


Actually, @eli_slade, all you need is this:

LongPressGesture().onChanged {_ in print("down") }

😀

Here's a demonstration app:

And here's the code for it:

struct ContentView: View {
    @State var isRed = false

    var body: some View {
        Circle()
            .fill(isRed ? Color.red : Color.black)
            .frame(width: 150, height: 150)
            .gesture(LongPressGesture().onChanged { _ in self.isRed.toggle()})
    }
}


来源:https://stackoverflow.com/questions/58784684/how-do-you-detect-a-swiftui-touchdown-event-with-no-movement-or-duration

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!