我偶然发现了与SwiftUI的ScrollView
相关的事件冒泡问题,并且能够将其简化为一小段代码。请看下面:
struct ContentView: View {
var body: some View {
ScrollView() {
Rectangle()
.fill(Color.red)
.frame(width: 200, height: 200)
.onTapGesture {
print("Rectangle onTapGesture")
}
}
.onTapGesture {
print("ScrollView onTapGesture")
}
}
}
在矩形外部点击时,我会在控制台中看到:
ScrollView onTapGesture
但是,点击矩形时,我看到正在打印两行:
ScrollView onTapGesture
Rectangle onTapGesture
ScrollView似乎也正在响应其子事件...这不应该发生,对吧?我该怎么做才能阻止这种情况?
编辑:只是为了增加疯狂,这两行并不总是以相同的顺序出现!我已经看到它们在重新启动应用程序时被交换了,而无需更改代码。
我的目标是在ScrollView上使用onTapGesture来捕获“关闭的”水龙头,即未被ScrollView的任何子级捕获/处理的水龙头。
非常感谢您!
答案 0 :(得分:3)
这是使用GeometryReader
和VStack
容器存储ScrollView
内容的简单解决方案:
struct ContentView: View {
var body: some View {
GeometryReader { contentView in
ScrollView {
VStack {
Rectangle()
.fill(Color.red)
.frame(width: 200, height: 200)
.onTapGesture {
print("Rectangle onTapGesture")
}
}
.frame(minWidth: contentView.size.width, minHeight: contentView.size.height, alignment: .top)
.contentShape(Rectangle())
.onTapGesture {
print("ScrollViewArea onTapGesture")
}
}
}
}
}
这将为您提供一个VStack
容器,该容器的大小始终与其父容器ScrollView
完全相同,因为我们从GeometryReader
的{{1}}属性中获得了动态值。
请注意,该容器上的size
可以使其像正常的alignment: .top
一样工作,将滚动项固定在顶部。额外的好处是,如果删除了ScrollView
属性,则滚动项将从屏幕中间开始,这是我发现在解决该问题之前无法做的事情。这在UX方面可能是有趣的,因为较短的列表在垂直居中可能有意义。我离题了。
最后的注释是alignment
修饰符,用于使新的.contentShape
的空白可用,可以解决您的问题。
这个想法来自 Swift攻击 ScrollView effects using GeometryReader article,概述了如何将这个想法推向另一个层次,并在滚动时转换元素。很好玩的东西!
答案 1 :(得分:2)
Gesture
与以前的UITapGestureRecognizer
工作方式不同。即默认情况下它同时识别手势。如果只想捕获具有优先权的某些手势,则需要在手势上使用ExclusiveGesture
视图修饰符,而不要使用默认的onTapGesture
。
TapGesture
错误地检测到触摸,因为它的响应速度太快(最小持续时间约为0.0001s)。我可以通过用更合理的TapGesture
代替minimumDuration
来解决此问题:
LongPressGesture(minimumDuration: 0.001)
请注意,您可以根据需要减少或增加最短持续时间,但这是我测试时最稳定的时间。
顺便说一句,这可能是 SwiftUI错误,我鼓励您提交概述该问题的错误报告。
这是代码:
struct ContentView: View {
var tap: some Gesture {
LongPressGesture(minimumDuration: 0.001)
.exclusively(before: scrollTap)
.onEnded { _ in
print("Rectangle onTapGesture")
}
}
var scrollTap: some Gesture {
LongPressGesture(minimumDuration: 0.001)
.onEnded { _ in
print("ScrollView onTapGesture")
}
}
var body: some View {
ScrollView() {
Rectangle()
.fill(Color.red)
.frame(width: 200, height: 200)
.gesture(tap)
}.gesture(scrollTap, including: .gesture)
}
}
如果您要独占忽略多个视图,请多次使用exclusively
视图修饰符(链接),或者如果要反转捕获顺序,请使用sequenced
。您可以查看Gesture docs了解详情。