英文:
How to make LongPressGesture and scrolling in ScrollView work together at the same time in SwiftUI?
问题
以下是您要翻译的代码部分:
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()
}
}
希望这对您有所帮助。
英文:
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()
}
}
答案1
得分: 2
我能够通过使用按钮而不是 TextView 来使其工作。虽然这直接使用了您提供的代码,但您应该能够修改一些部分以使其满足您的需求(如果需要,我可以帮忙!)
import SwiftUI
struct ScrollTest: View {
let testData = [1]
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
AnimatedButtonView(color: .red, text: "测试1")
AnimatedButtonView(color: .green, text: "测试2")
AnimatedButtonView(color: .blue, text: "测试3")
}
}
}
struct AnimatedButtonView: View {
@GestureState var isDetectingLongPress = false
let color: Color
let text: String
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 12.5, style: .continuous)
.fill(color)
.frame(width: UIScreen.main.bounds.width, height: 200)
.padding(25)
.scaleEffect(!isDetectingLongPress ? 1.0 : 0.875)
.brightness(!isDetectingLongPress ? 0.0 : -0.125)
.animation(.easeInOut(duration: 0.125), value: isDetectingLongPress)
Text(text)
}
.delaysTouches(for: 0.01) {
//some code here, if needed
}
.gesture(
LongPressGesture(minimumDuration: 3)
.updating($isDetectingLongPress) { currentState, gestureState,
transaction in
gestureState = currentState
transaction.animation = Animation.easeIn(duration: 2.0)
}
.onEnded { finished in
print("手势结束")
})
}
}
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
}
}
}
英文:
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!)
import SwiftUI
struct ScrollTest: View {
let testData = [1]
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
AnimatedButtonView(color: .red, text: "Test 1")
AnimatedButtonView(color: .green, text: "Test 2")
AnimatedButtonView(color: .blue, text: "Test 3")
}
}
}
struct AnimatedButtonView: View {
@GestureState var isDetectingLongPress = false
let color: Color
let text: String
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 12.5, style: .continuous)
.fill(color)
.frame(width: UIScreen.main.bounds.width, height: 200)
.padding(25)
.scaleEffect(!isDetectingLongPress ? 1.0 : 0.875)
.brightness(!isDetectingLongPress ? 0.0 : -0.125)
.animation(.easeInOut(duration: 0.125), value: isDetectingLongPress)
Text(text)
}
.delaysTouches(for: 0.01) {
//some code here, if needed
}
.gesture(
LongPressGesture(minimumDuration: 3)
.updating($isDetectingLongPress) { currentState, gestureState,
transaction in
gestureState = currentState
transaction.animation = Animation.easeIn(duration: 2.0)
}
.onEnded { finished in
print("gesture ended")
})
}
}
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
}
}
}
答案2
得分: 0
我不确定我理解确切的上下文,但你可以添加一个条件,以便你的 LongPressGesture
仅在手势未用于滚动时触发操作。
let longTap = LongPressGesture(minimumDuration: 0.3)
.updating($isLongPressure) { value, state, transaction in
if value {
state = true
transaction.animation = .easeOut(duration: 0.2)
}
}
英文:
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.
let longTap = LongPressGesture(minimumDuration: 0.3)
.updating($isLongPressure) { value, state, transaction in
if value {
state = true
transaction.animation = .easeOut(duration: 0.2)
}
}
答案3
得分: 0
这是@nickreps解决方案的一个版本,但稍微更短一些:
struct MyView: View {
var body: some View {
ScrollView { VStack { ForEach(0..<40) { _ in Item() } } }
}
}
struct Item: View {
var body: some View {
Color.yellow
.frame(height: 100)
.border(Color.black, width: 1)
.yieldTouches() // <- 这解决了手势冲突
.gesture(
LongPressGesture(minimumDuration: 1, maximumDistance: 10)
.onEnded { _ in print("你好!") }
)
}
}
public extension View {
func yieldTouches() -> some View { modifier(YieldTouches()) }
}
private struct YieldTouches: ViewModifier {
@State private var disabled = false
func body(content: Content) -> some View {
content
.disabled(disabled)
.onTapGesture { onMain { disabled = true; onMain { disabled = false } } }
}
private func onMain(_ action: @escaping () -> Void) { DispatchQueue.main.async(execute: action) }
}
英文:
Here is a version of @nickreps solution, but a bit shorter:
struct MyView : View {
var body: some View {
ScrollView { VStack { ForEach(0..<40) { _ in Item() } } }
}
}
struct Item : View {
var body: some View {
Color.yellow
.frame(height: 100)
.border(Color.black, width: 1)
.yieldTouches() // <- this solves gestures conflict
.gesture(
LongPressGesture(minimumDuration: 1, maximumDistance: 10)
.onEnded { _ in print("hello!") }
)
}
}
public extension View {
func yieldTouches() -> some View { modifier(YieldTouches()) }
}
private struct YieldTouches : ViewModifier {
@State private var disabled = false
func body(content: Content) -> some View {
content
.disabled(disabled)
.onTapGesture { onMain { disabled = true; onMain { disabled = false } } }
}
private func onMain(_ action: @escaping () -> Void) { DispatchQueue.main.async(execute: action) }
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论