问题
I want to execute an action when the button press begins and then when the button stops being pressed. I was looking for a simple solution, but got into more complicated configurations. One option that is pretty simple and close is the one I got from BlueSpud. The button action is not used so I tried:
struct MyView: View {
@State private var pressing = false
var body: some View {
Text("Button")
.background(self.pressing ? Color.red : Color.blue)
.gesture(DragGesture(minimumDistance: 0.0)
.onChanged { _ in
self.pressing = true
print("Pressing started and/or ongoing")
}
.onEnded { _ in
self.pressing = false
print("Pressing ended")
})
}
}
The problem with this code is that if you drag your finger out of the button area while pressing, .onEnded never gets called, and without a reliable end to the event, the solution doesn't work.
I have also tried Apple's example for composing SwiftUI gestures. It provides a very consistent control over the pressed and unpressed states, but I can't seem to know where to insert my actions:
struct PressedButton: View {
var startAction: ()->Void
var endAction: ()->Void
enum DragState {
case inactive
case pressing
case dragging(translation: CGSize)
var translation: CGSize {
switch self {
case .inactive, .pressing:
return .zero
case .dragging(let translation):
return translation
}
}
var isActive: Bool {
switch self {
case .inactive:
print("DragState inactive but I can't add my action here")
//self.endAction()
return false
case .pressing, .dragging:
return true
}
}
var isDragging: Bool {
switch self {
case .inactive, .pressing:
return false
case .dragging:
return true
}
}
}
@GestureState var dragState = DragState.inactive
var body: some View {
let longPressDrag = LongPressGesture(minimumDuration: 0.1)
.sequenced(before: DragGesture())
.updating($dragState) { value, state, transaction in
switch value {
// Long press begins.
case .first(true):
print("Long press begins. I can add my action here")
self.startAction()
state = .pressing
// Long press confirmed, dragging may begin.
case .second(true, let drag):
//print("Long press dragging")
state = .dragging(translation: drag?.translation ?? .zero)
// Dragging ended or the long press cancelled.
default:
print("Long press inactive but it doesn't get called")
state = .inactive
}
}
.onEnded { _ in
print("Long press ended but it doesn't get called")
}
return Text("Button")
.background(dragState.isActive ? Color.purple : Color.orange)
.gesture(longPressDrag)
}
}
回答1:
As soon as native SwiftUI does not allow now what you want to achieve, I'd recommend the following approach, which is valid and manageable and, so, reliable.
The demo shows simplified code based on using UIGestureRecongnizer
/UIViewRepresentable
, which can be easily extended (eg. if you want to intercept touchesCanceled
, click count, etc.)
import SwiftUI
import UIKit
class MyTapGesture : UITapGestureRecognizer {
var didBeginTouch: (()->Void)?
var didEndTouch: (()->Void)?
init(target: Any?, action: Selector?, didBeginTouch: (()->Void)? = nil, didEndTouch: (()->Void)? = nil) {
super.init(target: target, action: action)
self.didBeginTouch = didBeginTouch
self.didEndTouch = didEndTouch
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
self.didBeginTouch?()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
self.didEndTouch?()
}
}
struct TouchesHandler: UIViewRepresentable {
var didBeginTouch: (()->Void)?
var didEndTouch: (()->Void)?
func makeUIView(context: UIViewRepresentableContext<TouchesHandler>) -> UIView {
let view = UIView(frame: .zero)
view.isUserInteractionEnabled = true
view.addGestureRecognizer(context.coordinator.makeGesture(didBegin: didBeginTouch, didEnd: didEndTouch))
return view;
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<TouchesHandler>) {
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
class Coordinator {
@objc
func action(_ sender: Any?) {
print("Tapped!")
}
func makeGesture(didBegin: (()->Void)?, didEnd: (()->Void)?) -> MyTapGesture {
MyTapGesture(target: self, action: #selector(self.action(_:)), didBeginTouch: didBegin, didEndTouch: didEnd)
}
}
typealias UIViewType = UIView
}
struct TestCustomTapGesture: View {
var body: some View {
Text("Hello, World!")
.padding()
.background(Color.yellow)
.overlay(TouchesHandler(didBeginTouch: {
print(">> did begin")
}, didEndTouch: {
print("<< did end")
}))
}
}
struct TestCustomTapGesture_Previews: PreviewProvider {
static var previews: some View {
TestCustomTapGesture()
}
}
来源:https://stackoverflow.com/questions/59363095/how-to-detect-and-take-action-when-a-button-is-being-pressed-in-swiftui