【问题标题】:How do I detect a touch on a UIBezierPath and move a ball along that?如何检测 UIBezierPath 上的触摸并沿其移动球?
【发布时间】:2011-01-31 17:32:59
【问题描述】:

如何沿着特定的 UiBezierPath 移动球?这可能吗?

我已经尝试了所有方法,包括使用

进行命中测试
-(BOOL)containsPoint:(CGPoint)point onPath:(UIBezierPath*)path inFillArea:(BOOL)inFill

如何检测路径上的触摸?

【问题讨论】:

  • 贝塞尔路径是一个只绘制事物的类。它将无法响应任何事件。
  • 没错,但是 CGContextPathContainsPoint 是一项了不起的工程,Antwan。它可以在任何 bez 路径上为您服务!
  • 运气好吗?你有解决办法吗?
  • (1) 这是一个绝对事实:不幸的是,绝对没有任何方法(Apple 提供)从 UIBezierPath 中提取点。估计,使用这个:stackoverflow.com/questions/4089443

标签: iphone objective-c cocoa-touch


【解决方案1】:

首先......这是一个绝对的事实:

Apple 提供的绝对没有方法,
从 UIBezierPath 中提取点

这是 2011 年 2 月的绝对事实,在最新的操作系统中,一切都即将到来。我已经与 Apple 的相关工程师讨论了这个问题。在许多游戏编程环境中这很烦人,但事实就是如此。那么,它可以回答你的问题吗?

第二点:不要忘记,沿着 UIBezierPath 制作动画就像馅饼一样简单。 SO上有很多答案,例如How can I animate the movement of a view or image along a curved path?

第三:关于找出触摸发生在哪里,如果这就是你要问的,正如 Cory 所说,这是一个难题!但是您可以使用 CGContextPathContainsPoint 来确定一个点(或触摸)是否给定厚度的路径上。

之后,假设我们讨论的是三次方,您需要沿着贝塞尔曲线找到点和可能的速度(也称为切线)。

这是执行此操作的确切总代码: Find the tangent of a point on a cubic bezier curve (on an iPhone)。我也贴在下面了。

根据您的情况,您必须实施自己的 MCsim 或迭代才能找到它的目标。这并不难。

(第四——作为脚注,有一个新事物,您可以逐步绘制 bezpath,可能与您的问题无关,而只是一个注释。)


为了方便大家以后阅读,这里是两个“好用”例程的链接问题的总结...

最后,这里以最简单的方式是计算等距点(实际上,近似等距)和这些点的切线的两个例程,沿着贝塞尔三次:

CGFloat bezierPoint(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d)
    {
    CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a );
    CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) );
    CGFloat C3 = ( (3.0 * b) - (3.0 * a) );
    CGFloat C4 = ( a );

    return ( C1*t*t*t + C2*t*t + C3*t + C4  );
    }

CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d)
    {
    CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a );
    CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) );
    CGFloat C3 = ( (3.0 * b) - (3.0 * a) );
    CGFloat C4 = ( a );

    return ( ( 3.0 * C1 * t* t ) + ( 2.0 * C2 * t ) + C3 );
    }

四个预先计算的值,C1 C2 C3 C4,有时被称为贝塞尔系数。 (回想一下,a b c d 通常被称为四个控制点。)当然,t 从 0 到 1,例如每 0.05 一次。 只需为 X 调用一次这些例程,为 Y 分别调用一次。 希望它可以帮助某人

【讨论】:

  • 路径上发生触摸的位置是个问题。但是如果我们知道路径上的每个点,就可以在路径上预点并在不可见动画后面使用以开始逐点移动。
  • 不错的答案。你的数学看起来比我发现的要简单得多。
  • 不能使用CGPathApply 从 UIBezierPath 中提取点吗?例如:CGPathApply([myBezierPath CGPath], myPointArray, myElementProcessorFunction).
  • @EFC 你好 EFC。不,CGPathApply 完全不相关。 CGPathApply 为您提供构成路径概念的各种“元素”(换句话说,您的“指令”,例如:从这里开始,画一条直线到这里,画一个二次曲线到某某,画一个圆圈)。它与沿数学创建的结果提取点毫无关系。可悲!
【解决方案2】:

bezierpath 类中有一个方法叫 containsPoint: .. 参考:http://developer.apple.com/library/ios/#documentation/2ddrawing/conceptual/drawingprintingios/BezierPaths/BezierPaths.html

并且您可以检测触摸点是否在贝塞尔路径对象中的天气。我已将其与我自己的方法一起使用,用户可以通过该方法轻松检测到贝塞尔路径上的触摸(如果存在圆形或闭合路径,则不是内部或外部)。

此代码允许用户在触摸它时选择一个贝塞尔路径绘图对象,并在其上出现一条带有动画的虚线。希望它可以帮助某人。 这是我自己项目的代码:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{

    if([[self tapTargetForPath:path] containsPoint:startPoint])  // 'path' is bezier path object
          {

                [self selectShape:currentSelectedPath];// now select a new/same shape

                NSLog(@"selectedShapeIndex: %d", selectedShapeIndex);

                break;
            }
}

// this method will let you easily select a bezier path ( 15 px up and down of a path drawing)
- (UIBezierPath *)tapTargetForPath:(UIBezierPath *)path
{
    if (path == nil) {
        return nil;
    }

    CGPathRef tapTargetPath = CGPathCreateCopyByStrokingPath(path.CGPath, NULL, fmaxf(35.0f, path.lineWidth), path.lineCapStyle, path.lineJoinStyle, path.miterLimit);
    if (tapTargetPath == NULL) {
        return nil;
    }

    UIBezierPath *tapTarget = [UIBezierPath bezierPathWithCGPath:tapTargetPath];
    CGPathRelease(tapTargetPath);
    return tapTarget;
}

-(void)selectShape:(UIBezierPath *)pathToSelect
{
    centerline = [CAShapeLayer layer];
    centerline.path = pathToSelect.CGPath;
    centerline.strokeColor = [UIColor whiteColor].CGColor;
    centerline.fillColor = [UIColor clearColor].CGColor;
    centerline.lineWidth = 1.0;
    centerline.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:10], [NSNumber numberWithInt:5], nil];
    [self.layer addSublayer:centerline];

    // showing animation on line
    CABasicAnimation *dashAnimation;
    dashAnimation = [CABasicAnimation animationWithKeyPath:@"lineDashPhase"];

    [dashAnimation setFromValue:[NSNumber numberWithFloat:0.0f]];
    [dashAnimation setToValue:[NSNumber numberWithFloat:30.0f]];
    [dashAnimation setDuration:1.0f];
    [dashAnimation setRepeatCount:10000];

    [centerline addAnimation:dashAnimation forKey:@"linePhase"];
}

【讨论】:

  • 在贝塞尔路径中并不意味着在贝塞尔路径上,当您触摸远离圆周线的贝塞尔曲线内部时,它会检测到误报
  • 从上面的代码中它将检测到圆周线上的正确触摸
【解决方案3】:

这是一个非常困难的问题。您需要确定最接近接触点的路径上的点,这是一个复杂的数学问题。这是我使用快速谷歌搜索能找到的最好的东西。 (注意:代码是 BASIC。)

http://www.tinaja.com/glib/bezdist.pdf

我承认,我很想自己写这篇文章。这会非常有用,我喜欢挑战。

【讨论】:

  • 科里!这比在 Mac 上要容易得多。您只需使用 CGContextPathContainsPoint。之后是三行代码来迭代并找到它触及的地方。
  • 我假设他想把球放在离手指最近的路径上。也许我弄错了。
【解决方案4】:

在数学上,您无法确定曲线内的点是否是纯数学的,因为曲线没有曲面。您可以做的是测试该点是否位于由CGPathCreateCopyByStrokingPathCGPathContainsPoint 创建的闭合曲线的边界所定义的曲面内。它创建填充的曲线以在屏幕上呈现曲线。

如果调用CGPathCreateCopyByStrokingPath,其参数:CGFloat lineWidth小于等于原曲线的最小曲率半径,则不会自相交。原始曲线也不应该自相交。

如果CGPathContainsPoint 返回true,并且我提到的两个条件都满足,那么您可以保证在曲线上找到一个且唯一一个距离lineWidth 的点。这没有代数解,所以你必须用这里描述的方法在曲线上找到一个接近接触点的近似点:Find a point, a given distance, along a simple cubic bezier curve. (On an iPhone!)

【讨论】:

    【解决方案5】:

    转:@rakesh 的回答

     func tapTargetForPath(_ path: CGPath) -> UIBezierPath? {
        let path = UIBezierPath(cgPath: path)
       guard let tapTargetPath = CGPath(__byStroking: path.cgPath,
               transform: nil,
               lineWidth: CGFloat(fmaxf(35.0, Float(path.lineWidth))),
               lineCap: path.lineCapStyle,
               lineJoin: path.lineJoinStyle,
               miterLimit: path.miterLimit) else {
                return nil
        }
    
        return UIBezierPath(cgPath: tapTargetPath)
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-12-09
      • 1970-01-01
      • 1970-01-01
      • 2011-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多