ios - 为什么 CGPath 包含测试有时会在路径外的点上返回 true?

标签 ios swift core-graphics hittest cgpath

我有一个 UIView 的子类,其中包含一堆使用 CGPAth 绘制的图形。我需要知道触摸何时触及这些路径之一,以及是哪一条。

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    for t in touches {
        for p in myDrawnPaths {
           if(p.contains(t.location(in: self)) {
              doStuffWith(p)
           }
        }
    }
}

有时,此代码会导致 doStuffWith() 在多个路径上执行,包括那些远离命中位置的路径。我做了一些检查,发现不应该受到影响的路径有些奇怪:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    for t in touches {
        for p in myDrawnPaths {
           print(p.contains(t.location(in: self))) //true
           print(p.boundingBox.contains(t.location(in: self))) //false!
        }
    }
}

嗯?边界框不应该包含整个路径,这意味着路径内的点一定在边界框内吗?

问题仅在此类测试中出现——当我使用 CoreGraphics API 以相同的 CGPath 绘制和动画时,所有显示都是正确的。

更新

我试图在 Playgrounds 中通过简单的实验路径重现该问题,但无法重现,因此我不得不从我的实际应用中获取它。

(75.0, 264.0) is in 
Path 0x600003643960:
  moveto (252.02, 287.067)
    lineto (259.489, 286.324)
    lineto (259.567, 286.754)
    lineto (263.438, 286.167)
    lineto (268.131, 285.503)
    lineto (268.17, 285.268)
    lineto (268.248, 284.681)
    lineto (269.069, 283.235)
    lineto (269.851, 282.57)
    lineto (269.773, 280.576)
    lineto (270.399, 279.95)
    lineto (270.829, 279.833)
    lineto (270.907, 278.425)
    lineto (271.494, 277.252)
    lineto (271.924, 277.486)
    lineto (272.002, 277.721)
    lineto (272.315, 277.799)
    lineto (273.058, 277.408)
    lineto (272.901, 273.85)
    lineto (271.65, 270.643)
    lineto (270.751, 267.085)
    lineto (269.812, 265.834)
    lineto (268.796, 265.13)
    lineto (268.17, 265.56)
    lineto (266.645, 266.264)
    lineto (265.902, 268.219)
    lineto (264.846, 269.666)
    lineto (264.416, 269.9)
    lineto (263.83, 269.666)
    curveto (263.83, 269.666) (262.813, 269.079) (262.891, 268.845)
    curveto (262.969, 268.61) (263.087, 266.889) (263.087, 266.889)
    lineto (264.416, 266.381)
    lineto (264.729, 265.052)
    lineto (264.964, 264.035)
    lineto (265.902, 263.409)
    lineto (265.785, 259.499)
    lineto (265.159, 258.6)
    lineto (264.651, 258.287)
    lineto (264.338, 257.466)
    lineto (264.651, 257.153)
    lineto (265.276, 257.27)
    lineto (265.355, 256.644)
    lineto (264.338, 255.784)
    lineto (263.83, 254.767)
    lineto (262.813, 254.767)
    lineto (261.053, 254.181)
    lineto (258.903, 252.851)
    lineto (257.847, 252.851)
    lineto (257.612, 253.086)
    lineto (257.221, 252.891)
    lineto (256.009, 251.991)
    lineto (254.875, 252.695)
    lineto (253.741, 253.594)
    lineto (253.858, 255.002)
    lineto (254.249, 255.119)
    lineto (255.07, 255.315)
    lineto (255.266, 255.628)
    lineto (254.249, 255.941)
    lineto (253.233, 256.058)
    lineto (252.646, 256.762)
    lineto (252.529, 257.583)
    lineto (252.646, 258.209)
    lineto (252.763, 260.359)
    lineto (251.356, 261.18)
    lineto (251.121, 261.102)
    lineto (251.121, 259.46)
    lineto (251.629, 258.521)
    lineto (251.864, 257.583)
    lineto (251.551, 257.27)
    lineto (250.808, 257.583)
    lineto (250.417, 259.225)
    lineto (249.361, 259.655)
    lineto (248.657, 260.398)
    lineto (248.579, 260.789)
    lineto (248.814, 261.102)
    lineto (248.579, 262.119)
    lineto (247.68, 262.314)
    lineto (247.68, 262.745)
    lineto (247.993, 263.683)
    lineto (247.563, 266.068)
    lineto (246.937, 267.632)
    lineto (247.172, 269.47)
    lineto (247.367, 269.9)
    lineto (247.054, 270.839)
    lineto (246.937, 271.152)
    lineto (246.82, 272.208)
    lineto (248.227, 274.554)
    lineto (249.361, 277.095)
    lineto (249.948, 278.972)
    lineto (249.635, 280.81)
    lineto (249.244, 283.156)
    lineto (248.306, 285.19)
    lineto (248.188, 286.246)
    lineto (246.937, 287.458)
    closepath
  moveto (233.916, 259.147)
  moveto (233.407, 258.717)
  moveto (232.703, 254.65)
  moveto (231.257, 254.142)
  moveto (230.592, 253.242)
  moveto (225.665, 252.148)
  moveto (224.57, 251.717)
  moveto (221.403, 250.857)
  moveto (218.352, 250.466)
  moveto (216.827, 248.394)
  moveto (217.101, 248.198)
  moveto (218.157, 247.885)
  moveto (219.565, 246.986)
    lineto (219.565, 246.595)
    lineto (219.799, 246.36)
    lineto (222.145, 245.969)
    lineto (223.084, 245.226)
    lineto (224.804, 244.405)
    lineto (224.883, 243.897)
    lineto (225.626, 242.763)
    lineto (226.33, 242.45)
    lineto (226.838, 241.746)
    lineto (227.737, 240.847)
    lineto (229.458, 239.908)
    lineto (231.296, 239.713)
    lineto (231.726, 240.143)
    lineto (231.608, 240.534)
    lineto (230.162, 240.925)
    lineto (229.575, 242.137)
    lineto (228.676, 242.45)
    lineto (228.48, 243.388)
    lineto (227.542, 244.64)
    lineto (227.424, 245.656)
    lineto (227.737, 245.852)
    lineto (228.128, 245.422)
    lineto (229.536, 244.288)
    lineto (230.044, 244.796)
    lineto (230.944, 244.796)
    lineto (232.195, 245.187)
    lineto (232.782, 245.617)
    lineto (233.368, 246.83)
    lineto (234.424, 247.885)
    lineto (235.949, 247.807)
    lineto (236.535, 247.416)
    lineto (237.161, 247.924)
    lineto (237.787, 248.12)
    lineto (238.295, 247.807)
    lineto (238.725, 247.807)
    lineto (239.351, 247.416)
    lineto (240.915, 246.008)
    lineto (242.245, 245.578)
    lineto (244.825, 245.461)
    lineto (246.585, 244.718)
    lineto (247.602, 244.21)
    lineto (248.188, 244.288)
    lineto (248.188, 246.517)
    lineto (248.384, 246.634)
    lineto (249.518, 246.947)
    lineto (250.261, 246.751)
    lineto (252.646, 246.126)
    lineto (253.076, 245.696)
    lineto (253.663, 245.891)
    lineto (253.663, 248.628)
    lineto (254.914, 249.84)
    lineto (255.422, 250.075)
    lineto (255.931, 250.466)
    lineto (255.422, 250.583)
    lineto (255.109, 250.466)
    lineto (253.663, 250.271)
    lineto (252.842, 250.505)
    lineto (251.942, 250.427)
    lineto (250.691, 251.014)
    lineto (249.987, 251.014)
    lineto (247.719, 250.505)
    lineto (245.686, 250.583)
    lineto (244.943, 251.6)
    lineto (242.205, 251.835)
    lineto (241.267, 252.148)
    lineto (240.837, 253.36)
    lineto (240.328, 253.79)
    lineto (240.133, 253.712)
    lineto (239.546, 253.086)
    lineto (237.787, 254.025)
    lineto (237.552, 254.025)
    lineto (237.122, 253.399)
    lineto (236.809, 253.477)
    lineto (236.066, 255.198)
    lineto (235.675, 256.762)
    lineto (234.424, 259.46)
    closepath
  moveto (222.849, 237.367)
  moveto (223.553, 236.545)
  moveto (224.413, 236.233)
  moveto (226.525, 234.708)
  moveto (227.424, 234.473)
  moveto (227.62, 234.668)
  moveto (225.626, 236.663)
  moveto (224.335, 237.406)
  moveto (223.514, 237.758)
    closepath
  moveto (257.221, 250.31)
  moveto (257.456, 251.287)
  moveto (258.707, 251.365)
  moveto (259.215, 250.896)
    curveto (259.215, 250.896) (259.176, 250.31) (259.059, 250.271)
    curveto (258.942, 250.192) (258.433, 249.528) (258.433, 249.528)
    lineto (257.573, 249.606)
    lineto (256.947, 249.684)
    lineto (256.83, 250.114)

正如我们所见,路径中所有点的 X 坐标都在 200 秒内,但 X 坐标为 75 的点被计算为在该路径内。

如果我将 close 命令附加到路径,问题就会消失,但这会带来其他问题:

  1. CGPath 的 contains 如何在开放路径上工作,如果有的话,记录在哪里?

  2. 为什么这会影响 contains 方法的结果,而不是我在屏幕上看到的任何内容?例如,如果我告诉相应的 CALayer 填充某种颜色,该颜色不会超出应有的位置。

最佳答案

在 Playground 上进行试验后,我发现 CGRect.contains(:) 方法对于 x 值大于或等于的点返回 false > maxX 值或大于或等于 maxY 值的 y 值。

事实证明,CGRect 的 documentation 说了类似的话:

如果一个点的坐标位于矩形内部或最小 X 或最小 Y 边上,则该点被视为在矩形内部。

我认为文档措辞不佳,因为最大 X 和最小 Y 边的交点未包含在最小 X 和最大 Y 边的交点中。

下面是演示这个的代码:

let rect = CGRect(x: 0, y: 0, width: 200, height: 200)
rect.contains(CGPoint(x: 0, y: 0))                 // true
rect.contains(CGPoint(x: rect.midX, y: 0))         // true
rect.contains(CGPoint(x: 0, y: rect.midY))         // true
rect.contains(CGPoint(x: 200, y: 200))             // false
rect.contains(CGPoint(x: 200, y: 0))               // false
rect.contains(CGPoint(x: 0, y: 200))               // false
rect.contains(CGPoint(x: 0, y: rect.maxY))         // false
rect.contains(CGPoint(x: rect.maxX, y: 0))         // false
rect.contains(CGPoint(x: rect.maxX, y: rect.maxY)) // false
rect.maxX                                          // 200
rect.maxY                                          // 200

此外,documentation 声明 boundingBox 包含路径的所有点,包括贝塞尔曲线和二次曲线的控制点。您可能想改用 boundingBoxOfPath

关于ios - 为什么 CGPath 包含测试有时会在路径外的点上返回 true?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52797372/

相关文章:

ios - 具有存储字符串日期的 Firebase 云消息传递

ios - 重写 UIView 子类中的 drawRect 方法并将其调用到另一个 UIViewcontroller 中

ios - 将按钮添加到导航 Controller

ios - 将 2 个 PHFetchResults 组合在一起

ios - Apple 证书对 PKCS7_verify 无效

swift - UICollectionView 未使用自定义 UIViewController 更新

ios - 使 UIPanGesture 跟随线条

ios - 侧面菜单显示不正确

cocoa - PicHandle 到 CGImageRef

ios - 如何在 iOS 中绘制没有抗锯齿/插值的位图?