SwiftUI ScrollView的onTapGesture被称为子事件

时间:2020-06-01 21:47:42

标签: ios swift swiftui

我偶然发现了与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的任何子级捕获/处理的水龙头。

非常感谢您!

2 个答案:

答案 0 :(得分:3)

这是使用GeometryReaderVStack容器存储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)

SwiftUI中的

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了解详情。