我想允许用户绘制曲线,使得没有线可以跨越另一条线,甚至是自己.绘制曲线是没有问题的,我甚至发现我可以创建一个关闭的路径,并且仍然很漂亮的线条,通过向前和向后跟踪线的节点,然后关闭路径.
不幸的是,iOS只提供一个点是否包含在封闭的路径(containsPoint:和CGPathContainsPoint)中的测试.不幸的是,用户可以很容易地将手指移动得足够快,使得触摸点落在现有路径的两侧,而实际上不被该路径所包含,因此测试接触点是相当毫无意义的.
我找不到路径方法的“交集”.
关于如何完成这项任务的任何其他想法?
解决方法
嗯,我没有想出办法这样做.这是不完美的,但我认为其他人可能想看到这个技术,因为这个问题被提升了几次.我使用的技术将所有要测试的项目绘制到位图上下文中,然后将进度行的新段落绘制到另一个位图上下文中.使用按位运算符比较这些上下文中的数据,如果发现任何重叠,则声明命中.
这种技术背后的想法是对所有先前绘制的线测试新绘制的线的每个片段,甚至对同一行的较早片段进行测试.换句话说,这种技术将检测线何时穿过另一条线,并且当它跨过自身时.
演示该技术的示例应用程序可用:LineSample.zip.
命中测试的核心是在我的LineView对象中完成.这里有两个关键的方法:
- (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: https://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.y); CGContextStrokePath(hitProgressContext); } } else { line.Failed = YES; [linesFailed addObject:line]; } return ok; }
我很乐意听到建议或看到改进.一方面,只需检查新段落的边界,而不是整个视图,将会更快.