您如何检测没有移动或持续时间的 SwiftUI touchDown 事件?
Posted
技术标签:
【中文标题】您如何检测没有移动或持续时间的 SwiftUI touchDown 事件?【英文标题】:How do you detect a SwiftUI touchDown event with no movement or duration? 【发布时间】:2019-11-10 00:06:38 【问题描述】:我试图检测手指何时第一次接触 SwiftUI 中的视图。我可以使用 UIKit Events 轻松做到这一点,但在 SwiftUI 中无法解决。
我尝试了最小移动为 0 的 DragGesture,但在您的手指移动之前它仍然不会改变。
TapGesture 仅在您抬起手指时才会起作用,而且无论我将参数设置为什么,LongPressGesture 都不会足够快地触发。
DragGesture(minimumDistance: 0, coordinateSpace: .local).onChanged( _ in print("down"))
LongPressGesture(minimumDuration: 0.01, maximumDistance: 100).onEnded(_ in print("down"))
我想在手指接触到视图时立即检测到 touchDown 事件。 Apple 的默认手势对距离或时间都有限制。
更新:这不再是问题了,因为 Apple 似乎更新了 DragGesture 的工作方式,或者我可能遇到了特定的上下文错误。
【问题讨论】:
必须在 TapGesture 上加上 .updating 修饰符,但不知道怎么做。 【参考方案1】:您可以像这样使用.updating
修饰符:
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)
一些注意事项:
最小距离为零可确保立即识别手势@GestureState
属性包装器会在手势结束时自动将其值重置为原始值。这样您只需要担心将isTapped
设置为true
。交互结束时会自动再次变为false
。
updating
修饰符有一个带有三个参数的奇怪闭包。在这种情况下,我们只对中间那个感兴趣。这是GestureState
的包装值的inout
参数,所以我们可以在这里设置它。第一个参数是手势的当前值;第三个是Transaction
,包含一些动画上下文。
【讨论】:
@eli_slade 这个答案应该被接受为最佳答案恕我直言 同样,最小距离为零是我在寻找解决方案时缺少的最重要的东西。【参考方案2】:如果你结合这两个问题的代码:
How to detect a tap gesture location in SwiftUI?
UITapGestureRecognizer - make it work on touch down, not touch up?
你可以做这样的事情:
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
我们确实可以进行一些抽象,以便其用法更像其他 SwiftUI 手势,但这是一个开始。希望 Apple 会在某个时候对此提供支持。
【讨论】:
【参考方案3】:您可以这样创建视图修饰符:
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
)
现在你可以像这样使用它:
struct MyView: View
var body: some View
Text("Hello World")
.onTouchDownGesture
print("View did tap!")
【讨论】:
这很好用,我喜欢这个代码。但感觉和 onTapGesture 一样。【参考方案4】:其实,@eli_slade,你只需要这个:
LongPressGesture().onChanged _ in print("down")
?
这是一个演示应用:
这是它的代码:
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())
【讨论】:
【参考方案5】:这是一种检测状态之间变化以及触摸坐标(在本例中为Text View
)的解决方案:
我添加了一个枚举来管理状态(对那些 UIKit-nostalgic 使用开始、移动和结束)
导入 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)"
【讨论】:
这也是不错的代码。但它有同样的问题。我检测到触地得分,但直到我移除触摸后才报告它,这与点击相同。 它现在确实检测到触摸,并立即报告。我可能在您的评论中遗漏了一些东西......我正在使用 ios 13 的 iPhone XS 进行测试 我会再检查一次。在我把手指从屏幕上移开之前,我觉得它不起作用。【参考方案6】:对于 iOS,这是一个检测所有手势状态的解决方案。使用来自Joe's answer under this question 的代码。
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>)
用法
TouchDownView state in
switch state
case .began: print("gesture began")
case .ended: print("gesture ended")
case .cancelled, .failed: print("gesture cancelled/failed")
default: break
【讨论】:
【参考方案7】:这是一个watchOS 特定的答案,因为isTouchedDown
定义中的内容在iOS 上以一种奇怪的方式被多次调用。如果你想在你的视图被向上/向下触摸时运行动作,iOS 有更好的解决方案,例如my other answer。
注意:当容器屏幕出现时,isTouchedDown
定义仍会触发几次。防止这种情况发生的一个 hacky 解决方案是布尔值 shouldCallIsTouchedDownCode
,它在 onAppear
中的 0.3 秒后变为 false。
@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
【讨论】:
【参考方案8】:我已经回答了这个here,但也值得发布这个更受欢迎的问题。
您可以在公开的View
上使用隐藏的_onButtonGesture
方法。它甚至不需要附加到Button
,但它看起来更好,因为你看到了按下效果。
代码:
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")")
结果:
由于帧速率的原因,它作为 GIF 看起来不太好,但每当你按下pressing
时,就会转到true
,然后在发布时false
。
【讨论】:
以上是关于您如何检测没有移动或持续时间的 SwiftUI touchDown 事件?的主要内容,如果未能解决你的问题,请参考以下文章
您如何在移动 safari 上检测 3G 与 Wifi 连接?
如何使用 SwiftUI 和 Combine 检测 Datepicker 的值变化?