2011-06-26 59 views
12

我想允許用戶以這樣的方式繪製曲線,即沒有線條可以穿過另一條線或甚至自身。繪製曲線是沒有問題的,我甚至發現我可以創建一條封閉的路徑,並且通過追蹤線路的節點並返回然後關閉路徑,仍然非常像線條一樣。在iOS中繪製線條時點擊檢測

不幸的是,iOS只提供了一個測試點,是否包含在一個封閉的路徑(containsPoint:和CGPathContainsPoint)中。不幸的是,用戶可以非常容易地將手指移動得足夠快,使得觸摸點落在現有路徑的兩側而不實際上被該路徑包含,因此測試觸摸點是非常沒有意義的。

我找不到路徑的任何「交點」方法。

有關如何完成此任務的其他想法?

+0

這個問題類似於另一個SO問題。 http://stackoverflow.com/questions/1021801/cgpathref-intersection那些答案建議看看每個單獨的像素,這將是緩慢的。您可以通過myBezierPath從您的UIBezierPath對象中獲取CGPathRef.CGPath – Andrew

+0

對類似問題的好處。我正在研究比較連續位圖的方法。一旦我有演示代碼,我會把它放在這裏。同時,我也會查看關於這個問題的答案。 – EFC

回答

6

嗯,我確實想出了一個辦法來做到這一點。這是不完美的,但我認爲其他人可能想看到這個技術,因爲這個問題已經提出了幾次。我使用的技術將所有要測試的項目繪製到位圖上下文中,然後將進度線的新段繪製到另一個位圖上下文中。這些上下文中的數據使用按位運算符進行比較,如果發現任何重疊,則聲明一次。

這種技術背後的想法是測試新繪製的線條的每一個部分與所有先前繪製的線條以及甚至對同一線條的較早部分。換句話說,這種技術可以檢測到一條線穿過另一條線時,還可以檢測到線穿過另一條線時。

演示此技術的示例應用程序可用: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: 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; 
} 

我很樂意聽到建議或看到改進。首先,僅檢查新分段的邊界矩形而不是整個視圖會快很多。

+2

公平的警告:我已經在示例應用程序中發現了一些錯誤,所以請確保您注意自己的實現。基本的技術似乎可行,只是可以改進的一些實施問題。我會更多地調整樣本並保持更新,但我的主要焦點將在其他地方。 – EFC

+0

嗨@EFC,我對社區和新手iOS程序員有點新鮮,你能指出我具體在哪裏代碼防止相交嗎?我只需要那部分。 – EdSniper

+0

爲了防止相交,我只是想查看舊段和新段之間是否有任何位相同。 'if((completedData [i] | progressData [i])&segmentData [i]){'line是什麼實際測試。此測試來自http://stackoverflow.com/a/6515999/383737。 – EFC

相關問題