How do I make a SwiftUI gesture that keeps running code while the view is pressed
Use instead the following combination to track continuous pressing down
Tested with Xcode 11.4 / iOS 13.4
@GestureState var isLongPress = false // will be true till tap hold
var plusLongPress: some Gesture {
LongPressGesture(minimumDuration: 1).sequenced(before:
DragGesture(minimumDistance: 0, coordinateSpace:
.local)).updating($isLongPress) { value, state, transaction in
switch value {
case .second(true, nil):
state = true
// side effect here if needed
default:
break
}
}
}
How to run function when long press gesture detected and stopped?
I could solve my problem, hope it's useful for some of you:
@State private var isPressingDown: Bool = false
Image(systemName: "mic.circle")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 70)
.foregroundColor(Color.blue)
.onLongPressGesture(minimumDuration: 0.3){
self.isPressingDown = true
print("started")
}
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onEnded{ _ in
if self.isPressingDown{
self.isPressingDown = false
print("ended")
}
}
)
SwiftUI: Longpress Gesture Hold for only 1 Second
here is a very basic approach that you can build on, based on the code in:
https://adampaxton.com/make-a-press-and-hold-fast-forward-button-in-swiftui/
struct IgnitionDriveView: View {
@State private var timer: Timer?
@State var isLongPressD = false
@State var isLongPressR = false
@State private var showDriveAlert = true
@State private var showOutOfGasAlert = false
@State var distanceCovered: Float = 0.0
private func circleShape(isPressed: Binding<Bool>) -> some View {
Button(action: {
if isPressed.wrappedValue {
isPressed.wrappedValue.toggle()
timer?.invalidate()
}
}) {
ZStack {
Circle().strokeBorder(style: StrokeStyle(lineWidth: 2))
Circle().fill(isPressed.wrappedValue ? .white : .red)
}.frame(width: 100, height: 100, alignment: .center)
}
}
var body: some View {
VStack(alignment: .leading) {
Text("Distance Covered in Km: \(distanceCovered)").font(.headline)
ProgressView(value: distanceCovered > 0 ? distanceCovered : 0, total: 1000).frame(height: 40)
HStack {
ZStack {
circleShape(isPressed: $isLongPressD)
.simultaneousGesture(LongPressGesture(minimumDuration: 0.2).onEnded { _ in
isLongPressD = true
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
if distanceCovered < 1000 {
distanceCovered += 10
} else {
showOutOfGasAlert = true
}
})
})
Text("D").bold().padding().foregroundColor(.green).font(.title)
}.foregroundColor(.green)
Spacer()
ZStack {
circleShape(isPressed: $isLongPressR)
.simultaneousGesture(LongPressGesture(minimumDuration: 0.2).onEnded { _ in
isLongPressR = true
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
if distanceCovered > 0 {
distanceCovered -= 10
}
})
})
Text("R").bold().padding().foregroundColor(.blue).font(.title)
}.foregroundColor(.green)
}.padding()
}.alert("Press D to Drive and R to Reverse", isPresented: $showDriveAlert) {
Button("Okay") { showDriveAlert = false }
}.alert("You ran out of Gas, Reverse to Gas Station", isPresented: $showOutOfGasAlert) {
Button("Sucks, but fine!") { showOutOfGasAlert = false }
}
.padding()
}
}
SwiftUI run code periodically while button is being held down; run different code when it is just tapped?
Here is a solution - to get continuous pressing it needs to combine long press gesture with sequenced drag and add timer in handlers.
Updated: Tested with Xcode 11.4 / iOS 13.4 (in Preview & Simulator)
struct TimeEventGeneratorView: View {
var callback: () -> Void
private let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
var body: some View {
Color.clear
.onReceive(self.timer) { _ in
self.callback()
}
}
}
struct TestContinuousPress: View {
@GestureState var pressingState = false // will be true till tap hold
var pressingGesture: some Gesture {
LongPressGesture(minimumDuration: 0.5).sequenced(before:
DragGesture(minimumDistance: 0, coordinateSpace:
.local)).updating($pressingState) { value, state, transaction in
switch value {
case .second(true, nil):
state = true
default:
break
}
}.onEnded { _ in
}
}
var body: some View {
VStack {
Image(systemName: "chevron.left")
.background(Group { if self.pressingState { TimeEventGeneratorView {
print(">>>> pressing: \(Date())")
}}})
.gesture(TapGesture().onEnded {
print("> just tap ")
})
.gesture(pressingGesture)
}
}
}
How to detect and take action when a button is being pressed in SwiftUI
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()
}
}
How to run code when swiftui gesture recognizers begin
There is special @GestureState
, which can be used for such purpose. So, here is possible approach
struct TestGestureBegin: View {
enum Progress {
case inactive
case started
case changed
}
@GestureState private var gestureState: Progress = .inactive // initial & reset value
var body: some View {
VStack {
Text("Drag over me!")
}
.frame(width: 200, height: 200)
.background(Color.yellow)
.gesture(DragGesture(minimumDistance: 0)
.updating($gestureState, body: { (value, state, transaction) in
switch state {
case .inactive:
state = .started
print("> started")
case .started:
state = .changed
print(">> just changed")
case .changed:
print(">>> changing")
}
})
.onEnded { value in
print("x ended")
}
)
}
}
SwiftUI LongPressGesture takes too long to recognize when TapGesture also present
To having some multi gesture that fits every ones needs in projects, Apple has nothing offer than normal gesture, mixing them together to reach the wished gesture some times get tricky, here is a salvation, working without issue or bug!
Here a custom zero issue gesture called interactionReader, we can apply it to any View. for having LongPressGesture and TapGesture in the same time.
import SwiftUI
struct ContentView: View {
var body: some View {
Circle()
.fill(Color.yellow)
.frame(width: 150, height: 150)
.interactionReader(longPressSensitivity: 250, tapAction: tapAction, longPressAction: longPressAction, scaleEffect: true)
.animation(Animation.easeInOut(duration: 0.2))
}
func tapAction() { print("tap action!") }
func longPressAction() { print("longPress action!") }
}
struct InteractionReaderViewModifier: ViewModifier {
var longPressSensitivity: Int
var tapAction: () -> Void
var longPressAction: () -> Void
var scaleEffect: Bool = true
@State private var isPressing: Bool = Bool()
@State private var currentDismissId: DispatchTime = DispatchTime.now()
@State private var lastInteractionKind: String = String()
func body(content: Content) -> some View {
let processedContent = content
.gesture(gesture)
.onChange(of: isPressing) { newValue in
currentDismissId = DispatchTime.now() + .milliseconds(longPressSensitivity)
let dismissId: DispatchTime = currentDismissId
if isPressing {
DispatchQueue.main.asyncAfter(deadline: dismissId) {
if isPressing { if (dismissId == currentDismissId) { lastInteractionKind = "longPress"; longPressAction() } }
}
}
else {
if (lastInteractionKind != "longPress") { lastInteractionKind = "tap"; tapAction() }
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) {lastInteractionKind = "none"}
}
}
return Group {
if scaleEffect { processedContent.scaleEffect(lastInteractionKind == "longPress" ? 1.5: (lastInteractionKind == "tap" ? 0.8 : 1.0 )) }
else { processedContent }
}
}
var gesture: some Gesture {
DragGesture(minimumDistance: 0.0, coordinateSpace: .local)
.onChanged() { _ in if !isPressing { isPressing = true } }
.onEnded() { _ in isPressing = false }
}
}
extension View {
func interactionReader(longPressSensitivity: Int, tapAction: @escaping () -> Void, longPressAction: @escaping () -> Void, scaleEffect: Bool = true) -> some View {
return self.modifier(InteractionReaderViewModifier(longPressSensitivity: longPressSensitivity, tapAction: tapAction, longPressAction: longPressAction, scaleEffect: scaleEffect))
}
}
Related Topics
How to Force My Keyboard to Be Up on My Program's Start in Swift
iPad Multitasking Support Requires These Orientations
Disable Scroll on a Uiwebview Allowed
How Does Uiedgeinsetsmake Work
iOS 9 - "Attempt to Delete and Reload the Same Index Path"
Swift 3 How to Get Date for Tomorrow and Yesterday ( Take Care Special Case ) New Month or New Year
"File Not Found", "Linker Command Failed with Exit Code 1" in Xcode 4.5.1
How to Return Value After the Execution of the Block? Swift
Cordova: Start Specific iOS Emulator Image
Nslayoutconstraint Crashes Viewcontroller
How to Remove All Navigationbar Back Button Title
Swift - Segmented Control - Switch Multiple Views
Xcode 7 Uitests with Localized Ui
How to Loop Over Struct Properties in Swift
How to Properly Change My Status Bar Style in Swift 2/ iOS 9