编写SwiftUI你需要注意的点:clipped()不会影响hit testing

214 阅读2分钟

SwiftUI中的.clipped()修改器能够将视图的内容修剪到视图的bounds内,任何超出视图bounds的内容都会被裁剪。但是.clipped()并不会影响hit testing,被修剪的View依然能够接收到超出视图可见区域的tap和click事件

例子

  1. 我们有一个300x300的正方形,我们把它限制在100x100的frame内,并且在100x100的上面添加了边框以便我们能够看到它
  2. SwiftUI的view默认不会修剪内容,所以我们依然能够看到300x300的正方形
  3. 图里面蓝色的边框就是我们限制的100x100的frame边框
Rectangle()
  .fill(.orange.gradient)
  .frame(width: 300, height: 300)
  // Set view to 100×100 → renders out of bounds
  .frame(width: 100, height: 100)
  .border(.blue)

image.png

例子修改1

现在我们给100x100的frame添加.clipped修改器,再添加一个tap手势。 再在正方形上方添加一个button,来演示button没有被响应的情况

VStack {
  Button("You can't tap me!") {
    buttonTapCount += 1
  }
  .buttonStyle(.borderedProminent)

  Rectangle()
    .fill(.orange.gradient)
    .frame(width: 300, height: 300)
    .frame(width: 100, height: 100)
    .clipped()
    .onTapGesture {
      rectTapCount += 1
    }
}

当我们运行代码的时候就会发现点击button一点响应也没有。 这是因为我们将300x300的正方形限制在了100x100的frame里,虽然超出的部分不可见,但由于它是在button之后布局且它的控件布局遮盖了button,会先相应hit test,事件在这就断开了。所以根本点击不到button上

image.png

使用.contentShape 修复问题

.contentShape修改器定义了一个View可以被hit test的区域。 我们给100x100frame的view添加 .contentShape(Rectangle())将hit test限制在该区域内,这样button就能继续被响应了

image.png

注意 .contentShape(Rectangle())和.clipped()不能交换,他们是有顺序的。 SwiftUI中每应用一次 视图修改器,都会创建一个新的View,如果交换的话相当于把hit test限制在了300x300的frame内,并不能解决问题。我们需要将它限制在100x100的frame内

总结

  1. clipped视图修改器不会影响视图的hit test区域,同样的,clipShape也不影响hit test区域
  2. 通过.contentShape(Rectangle())和这些修改器组合来保证hit test逻辑和UI的一致是一个不错的做法

资料

oleb.net/2022/clippe…