Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 435 Vote(s) - 3.46 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How do you detect a SwiftUI touchDown event with no movement or duration?

#1
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.


Update: This is not an issue anymore as Apple has seemed to update how DragGesture works or maybe I was experiencing a specific contextual bug.
Reply

#2
None of the above worked perfectly, like the `DragGesture` made it impossible for the table to scroll

after long searching i found this holy grail!

Note: it is public available public, but the _ prefix means it may not be suited for production

but still works great

._onButtonGesture(pressing: { pressing in

}, perform: {

})
Reply

#3
All you need is this:

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

😀

Here's a demonstration app:

[![App Demo][1]][1]


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()})
}
}


[1]:
Reply

#4
I already answered this [here][1], but worth posting on this more popular question too.

---

You can use a hidden `_onButtonGesture` method on `View`, which is public. It doesn't even need to be attached to the `Button`, but it looks better since you see that pressing down effect.

Code:

```
struct ContentView: View {
@State private var counter = 0
@State private var pressing = false

var body: some View {
VStack(spacing: 30) {
Text("Count: \(counter)")

Button("Increment") {
counter += 1
}
._onButtonGesture { pressing in
self.pressing = pressing
} perform: {}

Text("Pressing button: \(pressing ? "yes" : "no")")
}
}
}
```

Result:

[![Result][2]][2]

_Doesn't look great as a GIF due to the frame rate, but whenever you press down `pressing` goes to `true`, then `false` on release._


[1]:

[To see links please register here]

[2]:
Reply

#5
This is a **watchOS-specific answer**, because the stuff that's inside the definition of `isTouchedDown` gets called multiple times in a weird way on iOS. There are better solutions for iOS if you want to run actions when your view is touched up/down, such as [my other answer][1].

---

Note: The `isTouchedDown` definition will still fire a couple of times when the container screen appears. A hacky solution to prevent that is the boolean `shouldCallIsTouchedDownCode` that turns false after 0.3 seconds in the `onAppear`.

@GestureState var longPressGestureState = false

@State var shouldCallIsTouchedDownCode = false

var isTouchedDown: Bool {
guard shouldCallIsTouchedDownCode else {
return
}

// use this place to call functions when the value changes

return longPressGestureState
}

var body: View {
Color(isTouchedDown ? .red : .black)
.gesture(
LongPressGesture(minimumDuration: .infinity, maximumDistance: .infinity)
.updating($longPressGestureState) { value, state, _ in
state = value
}
)
.onAppear {
shouldCallIsTouchedDownCode = false

DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
shouldCallIsTouchedDownCode = true
}
}
}


[1]:

[To see links please register here]

Reply

#6
For iOS, here's a solution that detects all gesture states. Using code from [Joe's answer under this question][1].

import SwiftUI
import UIKit

struct TouchDownView: UIViewRepresentable {
typealias TouchDownCallback = ((_ state: UIGestureRecognizer.State) -> Void)

var callback: TouchDownCallback

func makeUIView(context: UIViewRepresentableContext<TouchDownView>) -> TouchDownView.UIViewType {
let view = UIView(frame: .zero)

let gesture = UILongPressGestureRecognizer(
target: context.coordinator,
action: #selector(Coordinator.gestureRecognized)
)

gesture.minimumPressDuration = 0
view.addGestureRecognizer(gesture)

return view
}

class Coordinator: NSObject {
var callback: TouchDownCallback

init(callback: @escaping TouchDownCallback) {
self.callback = callback
}

@objc fileprivate func gestureRecognized(gesture: UILongPressGestureRecognizer) {
callback(gesture.state)
}
}

func makeCoordinator() -> TouchDownView.Coordinator {
return Coordinator(callback: callback)
}

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

# Usage

TouchDownView { state in
switch state {
case .began: print("gesture began")
case .ended: print("gesture ended")
case .cancelled, .failed: print("gesture cancelled/failed")
default: break
}
}


[1]:

[To see links please register here]

[2]:

[To see links please register here]

Reply

#7
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!")
}
}
}
Reply

#8
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)"
}
}

Reply

#9
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.
Reply

#10
If you combine the code from these two questions:

[To see links please register here]


[To see links please register here]


You can make something like this:
```Swift
ZStack {
Text("Test")
TapView {
print("Tapped")
}
}
```

```Swift
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.
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through