在iOS中绘制线时的点击检测 [英] Hit detection when drawing lines in iOS

查看:90
本文介绍了在iOS中绘制线时的点击检测的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想允许用户绘制曲线,使得没有线可以穿过另一条线或甚至自身。绘制曲线是没有问题的,我甚至发现,我可以创建一个关闭,仍然漂亮的线状的路径,通过跟踪线的节点向前和向后,然后关闭路径。

I would like to allow the user to draw curves in such a way that no line can cross another line or even itself. Drawing the curves is no problem, and I even found that I can create a path that is closed and still pretty line-like by tracing the nodes of the line forwards and back and then closing the path.

不幸的是,iOS只提供一个点是否包含在封闭路径(containsPoint:和CGPathContainsPoint)中的测试。不幸的是,用户可以很容易地足够快地移动他们的手指,使得触摸点落在现有路径的两侧而实际上不被该路径包含,因此测试触摸点是相当无意义的。

Unfortunately, iOS only provides a test for whether a point is contained in a closed path (containsPoint: and CGPathContainsPoint). Unfortunately, a user can pretty easily move their finger fast enough that the touch points land on both sides of an existing path without actually being contained by that path, so testing the touch points is pretty pointless.

我找不到任何交集的路径方法。

I can't find any "intersection" of paths method.

有关如何完成此任务的任何其他想法?

Any other thoughts on how to accomplish this task?

推荐答案

好吧,我想出了一个办法。这是不完美的,但我认为其他人可能想看到的技术,因为这个问题upvote几次。我使用的技术将所有要测试的项目绘制到位图上下文中,然后将进度线的新段绘制到另一个位图上下文中。在这些上下文中的数据使用逐位运算符进行比较,如果发现任何重叠,则命中命中。

Well, I did come up with a way to do this. It is imperfect, but I thought others might want to see the technique since this question was upvoted a few times. The technique I used draws all the items to be tested against into a bitmap context and then draws the new segment of the progressing line into another bitmap context. The data in those contexts is compared using bitwise operators and if any overlap is found, a hit is declared.

这种技术的想法是测试一个新的对于所有先前绘制的线,甚至相对于同一线的早先部分绘制线。

The idea behind this technique is to test each segment of a newly drawn line against all the previously drawn lines and even against earlier pieces of the same line. In other words, this technique will detect when a line crosses another line and also when it crosses over itself.

一个示例应用程序演示了该技术可用:LineSample.zip

A sample app demonstrating the technique is available: LineSample.zip.

点击测试的核心是在我的LineView对象中。这里有两个关键方法:

The core of hit testing is done in my LineView object. Here are two key methods:

- (CGContextRef)newBitmapContext {

    // creating b&w bitmaps to do hit testing
    // based on: http://robnapier.net/blog/clipping-cgrect-cgpath-531
    // see "Supported Pixel Formats" in Quartz 2D Programming Guide
    CGContextRef bitmapContext =
    CGBitmapContextCreate(NULL, // data automatically allocated
                          self.bounds.size.width,
                          self.bounds.size.height,
                          8, 
                          self.bounds.size.width,
                          NULL,
                          kCGImageAlphaOnly);
    CGContextSetShouldAntialias(bitmapContext, NO);
    // use CGBitmapContextGetData to get at this data

    return bitmapContext;
}


- (BOOL)line:(Line *)line canExtendToPoint:(CGPoint) newPoint {

    //  Lines are made up of segments that go from node to node. If we want to test for self-crossing, then we can't just test the whole in progress line against the completed line, we actually have to test each segment since one segment of the in progress line may cross another segment of the same line (think of a loop in the line). We also have to avoid checking the first point of the new segment against the last point of the previous segment (which is the same point). Luckily, a line cannot curve back on itself in just one segment (think about it, it takes at least two segments to reach yourself again). This means that we can both test progressive segments and avoid false hits by NOT drawing the last segment of the line into the test! So we will put everything up to the  last segment into the hitProgressLayer, we will put the new segment into the segmentLayer, and then we will test for overlap among those two and the hitTestLayer. Any point that is in all three layers will indicate a hit, otherwise we are OK.

    if (line.failed) {
        // shortcut in case a failed line is retested
        return NO;
    }
    BOOL ok = YES; // thinking positively

    // set up a context to hold the new segment and stroke it in
    CGContextRef segmentContext = [self newBitmapContext];
    CGContextSetLineWidth(segmentContext, 2); // bit thicker to facilitate hits
    CGPoint lastPoint = [[[line nodes] lastObject] point];
    CGContextMoveToPoint(segmentContext, lastPoint.x, lastPoint.y);
    CGContextAddLineToPoint(segmentContext, newPoint.x, newPoint.y);
    CGContextStrokePath(segmentContext);

    // now we actually test
    // based on code from benzado: http://stackoverflow.com/questions/6515885/how-to-do-comparisons-of-bitmaps-in-ios/6515999#6515999
    unsigned char *completedData = CGBitmapContextGetData(hitCompletedContext);
    unsigned char *progressData = CGBitmapContextGetData(hitProgressContext);
    unsigned char *segmentData = CGBitmapContextGetData(segmentContext);

    size_t bytesPerRow = CGBitmapContextGetBytesPerRow(segmentContext);
    size_t height = CGBitmapContextGetHeight(segmentContext);
    size_t len = bytesPerRow * height;

    for (int i = 0; i < len; i++) {
        if ((completedData[i] | progressData[i]) & segmentData[i]) { 
            ok = NO; 
            break; 
        }
    }

    CGContextRelease(segmentContext);

    if (ok) {
        // now that we know we are good to go, 
        // we will add the last segment onto the hitProgressLayer
        int numberOfSegments = [[line nodes] count] - 1;
        if (numberOfSegments > 0) {
            // but only if there is a segment there!
            CGPoint secondToLastPoint = [[[line nodes] objectAtIndex:numberOfSegments-1] point];
            CGContextSetLineWidth(hitProgressContext, 1); // but thinner
            CGContextMoveToPoint(hitProgressContext, secondToLastPoint.x, secondToLastPoint.y);
            CGContextAddLineToPoint(hitProgressContext, lastPoint.x, lastPoint.y);
            CGContextStrokePath(hitProgressContext);
        }
    } else {
        line.failed = YES;
        [linesFailed addObject:line];
    }
    return ok;
}

我很乐意听到建议或看到改进。一方面,只需检查新细分的边界矩形,而不是整个视图,就会快很多。

I'd love to hear suggestions or see improvements. For one thing, it would be a lot faster to only check the bounding rect of the new segment instead of the whole view.

这篇关于在iOS中绘制线时的点击检测的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆