【问题标题】:HitTest with CAShapeLayer doesn't work使用 CAShapeLayer 的 HitTest 不起作用
【发布时间】:2013-06-09 11:02:42
【问题描述】:

我使用以下代码检查玩家的点是否在圆形区域中:

if ([circle.presentationLayer hitTest:player.position])
{
    NSLog(@"hit");
}

我的圈子是一个 CAShapeLayer:

CAShapeLayer *circle = [CAShapeLayer layer];
CGFloat radius = 50; 
[circle setMasksToBounds:YES];
[circle setBackgroundColor:[UIColor redColor].CGColor];
[circle setCornerRadius:radius1];
[circle setBounds:CGRectMake(0.0f, 0.0f, radius *2, radius *2)];
[self.view.layer addSublayer:circle];

这样的碰撞检测效果很好。

现在我不想使用圆形图层来测试玩家的位置,而是使用沿自定义路径绘制的 CAShapeLayer:

CAShapeLayer *customLayer = [CAShapeLayer layer];
customLayer.path = customPath.CGPath;
customLayer.fillColor = [UIColor yellowColor].CGColor;
customLayer.shouldRasterize = YES;
customLayer.opacity = 0.2;
[self.view.layer addSublayer: customLayer];

当我想用自定义层对玩家的位置进行命中测试时,命中测试不再起作用。

我该如何解决这个问题?

【问题讨论】:

    标签: ios path collision-detection hittest cashapelayer


    【解决方案1】:

    您是否将位置转换为正确的相对位置?

    例如:

    CGPoint layerPoint = [[dynamicView layer] convertPoint:touchLocation toLayer:sublayer];
    

    另外,也许你需要CGPathContainsPoint:

    if(CGPathContainsPoint(shapeLayer.path, 0, layerPoint, YES))
    {
    

    【讨论】:

      【解决方案2】:

      您正在分配圆圈的边界/框架,而不是贝塞尔路径的边界/框架。直到我自己偶然发现它之前我才知道这一点,但是当您创建一个形状并将路径分配给 CAShapeLayer() 时,您必须分配 CALayer 的框架(或分别分配位置和边界)因为根据 Apple 的文档,CALayer() 的框架默认为 CGZeroRect,并且在您设置路径时不会自动更新。 hitTest() 基于位置+边界(或框架),因此失败。文档说:

      /* 返回包含点 'p' 的层的最远后代。 * 兄弟姐妹按从上到下的顺序搜索。 'p' 被定义为 * 在接收者最近祖先的坐标空间中 * 不是 CATransformLayer(变换层没有 2D * 可以指定点的坐标空间)。 */

      • (可为空的 CALayer *)hitTest:(CGPoint)p;

      /* 如果层的边界包含点 'p',则返回 true。 */

      • (BOOL)containsPoint:(CGPoint)p;

      快速的单元测试显示了这一点(扩展 CGPath 很有用,并且包含在之后):

      func testShapeLayer() {
          let layer = CAShapeLayer()
          let bezier = NSBezierPath(rect: NSMakeRect(0,0,10,10))
          layer.path = bezier.CGPath
          // remove the following line to FAIL the test
          layer.frame = NSMakeRect(0,0,10,10) 
          XCTAssertNotNil(layer.hitTest(CGPoint(x:5,y:5)))
          XCTAssertTrue(layer.contains(CGPoint(x:5,y:5)))
      }
      
      extension NSBezierPath {
      
      public var CGPath: CGPath {
          let path = CGMutablePath()
          var points = [CGPoint](repeating: .zero, count: 3)
          for i in 0 ..< self.elementCount {
              let type = self.element(at: i, associatedPoints: &points)
              switch type {
              case .moveToBezierPathElement: path.move(to: CGPoint(x: points[0].x, y: points[0].y) )
              case .lineToBezierPathElement: path.addLine(to: CGPoint(x: points[0].x, y: points[0].y) )
              case .curveToBezierPathElement: path.addCurve(      to: CGPoint(x: points[2].x, y: points[2].y),
                                                                  control1: CGPoint(x: points[0].x, y: points[0].y),
                                                                  control2: CGPoint(x: points[1].x, y: points[1].y) )
              case .closePathBezierPathElement: path.closeSubpath()
              }
          }
          return path
      }
      

      }

      然而,这可能不是您想要的:您希望路径用于 hitTesting(),而不仅仅是框架。因此,我可能会在您的 hitTesting 例程中添加一个扩展:如果 CAShapeLayer() 被命中,则意味着它在框架内,那么您可以更具体地检查 CGPathContainsPoint() ,就像提到的其他答案一样。

      【讨论】:

      • 路径可以在框架之外,所以最好只检查 path.contains(point) - 但一定要将点转换为图层坐标。通常,该点位于视图坐标中,如果图层位置已移动,则该坐标可能不同。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-07-22
      • 2018-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-28
      • 1970-01-01
      相关资源
      最近更新 更多