Let’s imagine, here is a ScrollView with some elements and I want to make some actions (e.g. changing of color) on long tap on these elements. But also I want to make possible to scroll this view.
Here is an example:
import SwiftUI
struct TextBox: View {
var text: String
var color: Color
@GestureState private var isLongPressure: Bool = false
var body: some View {
let longTap = LongPressGesture(minimumDuration: 0.3)
.updating($isLongPressure) { state, newState, transaction in
newState = state
transaction.animation = .easeOut(duration: 0.2)
}
Text(text)
.frame(width: 400, height: 200)
.background(isLongPressure ? .white : color)
.simultaneousGesture(longTap)
}
}
struct TestGestures: View {
var body: some View {
ScrollView {
TextBox(text: "Test 1", color: .red)
TextBox(text: "Test 2", color: .green)
TextBox(text: "Test 3", color: .blue)
TextBox(text: "Test 4", color: .red)
TextBox(text: "Test 5", color: .green)
TextBox(text: "Test 6", color: .blue)
}
}
}
struct TestGestures_Previews: PreviewProvider {
static var previews: some View {
TestGestures()
}
}
So, if I comment .simultaneousGesture(longTap)
– scrolling works, but if I uncomment it – scrolling stopped work.
P.S.: I’ve tried to add onTapGesture
before adding longTap and it doesn’t help.
Thanks in advance!
Update:
Thanks for the solution by @nickreps:
import SwiftUI
struct TextBox: View {
var text: String
var color: Color
@GestureState private var isLongPressure: Bool = false
var body: some View {
let longTap = LongPressGesture(minimumDuration: 0.3)
.updating($isLongPressure) { value, state, transaction in
state = value
transaction.animation = .easeOut(duration: 0.2)
}
Text(text)
.frame(width: 400, height: 200)
.background(isLongPressure ? .white : color)
.delaysTouches(for: 0.01) {
//some code here, if needed
}
.gesture(longTap)
}
}
struct TestGestures: View {
var body: some View {
ScrollView {
TextBox(text: "Test 1", color: .red)
TextBox(text: "Test 2", color: .green)
TextBox(text: "Test 3", color: .blue)
TextBox(text: "Test 4", color: .red)
TextBox(text: "Test 5", color: .green)
TextBox(text: "Test 6", color: .blue)
}
}
}
extension View {
func delaysTouches(for duration: TimeInterval = 0.25, onTap action: @escaping () -> Void = {}) -> some View {
modifier(DelaysTouches(duration: duration, action: action))
}
}
fileprivate struct DelaysTouches: ViewModifier {
@State private var disabled = false
@State private var touchDownDate: Date? = nil
var duration: TimeInterval
var action: () -> Void
func body(content: Content) -> some View {
Button(action: action) {
content
}
.buttonStyle(DelaysTouchesButtonStyle(disabled: $disabled, duration: duration, touchDownDate: $touchDownDate))
.disabled(disabled)
}
}
fileprivate struct DelaysTouchesButtonStyle: ButtonStyle {
@Binding var disabled: Bool
var duration: TimeInterval
@Binding var touchDownDate: Date?
func makeBody(configuration: Configuration) -> some View {
configuration.label
.onChange(of: configuration.isPressed, perform: handleIsPressed)
}
private func handleIsPressed(isPressed: Bool) {
if isPressed {
let date = Date()
touchDownDate = date
DispatchQueue.main.asyncAfter(deadline: .now() + max(duration, 0)) {
if date == touchDownDate {
disabled = true
DispatchQueue.main.async {
disabled = false
}
}
}
} else {
touchDownDate = nil
disabled = false
}
}
}
struct TestGestures_Previews: PreviewProvider {
static var previews: some View {
TestGestures()
}
}
2
Answers
I’m not sure I understand the exact context, but you could add a condition so your
LongPressGesture
only triggers an action when gesture is not being used for scrolling.I was able to get it working by utilizing a button rather than a TextView. Although this does directly utilize the code you provided, you should be able to modify some pieces to have it meet your needs (I can help with this, if needed!)