检测厚UIBezierPath

时间:2015-05-04 18:46:25

标签: ios objective-c quartz-graphics uibezierpath

在我的应用程序的绘图部分中,用户应该能够选择他们创建的路径。 UIBezierPath有一个方便的方法containsPoint:,在大多数情况下效果很好,但是当绘制的线条接近直线而没有曲线时,它非常糟糕。让你的手指直线是非常困难的。

似乎该方法只检查路径上的一条非常窄的线,甚至将路径的lineWidth属性设置为更大的值也不会影响containsPoint:测试的结果。

我已经查看了类似的问题,例如:Detect touch on UIBezierPath stroke, not fill但无法修改解决方案以查看"路径周围的区域",它似乎只是看起来像在它的中心的细线处。

修改

我提出的一个工作但密集的解决方案是将路径转换为UIImage并在请求的点检查该图像的颜色。如果alpha分量大于零,则路径与触摸相交。

这看起来像这样:

// pv is a view that contains our path as a property
// touchPoint is a CGPoint signifying the location of the touch
PathView *ghostPathView = [[PathView alloc] initWithFrame:pv.bounds];
ghostPathView.path = [pv.path copy]; // make a copy of the path
// 40 px is about thick enough to touch reliably
ghostPathView.path.lineWidth = 40; 
// the two magic methods getImage and getColorAtPoint: 
// have been copied from elsewhere on stackoverflow 
// and do exactly what you would expect from their names
UIColor *color = [[ghostPathView getImage] getColorAtPoint:touchPoint];
CGFloat alpha;
[color getWhite:nil alpha:&alpha];

if (alpha > 0){
// it's a match!
}

以下是辅助方法:

// in a category on UIImage
- (UIColor *)getColorAtPoint:(CGPoint)p {
    // return the colour of the image at a specific point

    // make sure the point lies within the image
    if (p.x >= self.size.width || p.y >= self.size.height || p.x < 0 || p.y < 0) return nil;

    // First get the image into your data buffer
    CGImageRef imageRef = [self CGImage];
    NSUInteger width = CGImageGetWidth(imageRef);
    NSUInteger height = CGImageGetHeight(imageRef);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    unsigned char *rawData = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
    NSUInteger bytesPerPixel = 4;
    NSUInteger bytesPerRow = bytesPerPixel * width;
    NSUInteger bitsPerComponent = 8;
    CGContextRef context = CGBitmapContextCreate(rawData, width, height,
                                                 bitsPerComponent, bytesPerRow, colorSpace,
                                                 kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(colorSpace);

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
    CGContextRelease(context);

    // Now your rawData contains the image data in the RGBA8888 pixel format.
    int byteIndex = [[UIScreen mainScreen] scale] * ((bytesPerRow * p.y) + p.x * bytesPerPixel);
    CGFloat red   = (rawData[byteIndex]     * 1.0) / 255.0;
    CGFloat green = (rawData[byteIndex + 1] * 1.0) / 255.0;
    CGFloat blue  = (rawData[byteIndex + 2] * 1.0) / 255.0;
    CGFloat alpha = (rawData[byteIndex + 3] * 1.0) / 255.0;

    free(rawData);

    return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}

// in a category on UIView
- (UIImage *)getImage {
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [[UIScreen mainScreen]scale]);
    [[self layer] renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return viewImage;
}

有更优雅的方法吗?

修改

Satachito的精彩回答,只是想把它包含在Obj-C中以保证完整性。我使用40磅笔画,因为它更接近Apple推荐的最小触摸目标尺寸为44x44像素。

CGPathRef pathCopy = CGPathCreateCopyByStrokingPath(originalPath.CGPath, nil, 40, kCGLineCapButt, kCGLineJoinMiter, 1);
UIBezierPath *fatOne = [UIBezierPath bezierPathWithCGPath:pathCopy];
if ([fatOne containsPoint:p]){
    // match found
}

2 个答案:

答案 0 :(得分:4)

使用'CGPathCreateCopyByStrokingPath'

    let org = UIBezierPath( rect: CGRectMake( 100, 100, 100, 100 ) )
    let tmp = CGPathCreateCopyByStrokingPath(
        org.CGPath
    ,   nil
    ,   10
    ,   CGLineCap( 0 )  //  Butt
    ,   CGLineJoin( 0 ) //  Miter
    ,   1
    )
    let new = UIBezierPath( CGPath: tmp )

制作原始路径的描边路径。并使用原始路径和描边路径进行测试。

答案 1 :(得分:2)

更新swift 3

  let path = UIBezierPath(arcCenter: arcCenter,
                                radius: radius,
                                startAngle: startAngle,
                                endAngle: endAngle,
                                clockwise: true)
        path.lineWidth = arcWidth
     let tmp:CGPath =  path.cgPath.copy(strokingWithWidth: arcWidth,
                                  lineCap: CGLineCap(rawValue: 0)!,
                                 lineJoin: CGLineJoin(rawValue: 0)!,
                                 miterLimit: 1)
        let new = UIBezierPath( cgPath: tmp )

更新目标c

 UIBezierPath* path = [[UIBezierPath alloc] init];
[path addArcWithCenter:arcCenter
                radius:radii
            startAngle:startAngle
              endAngle:endAngle
             clockwise:YES];
CGPathRef tmp = CGPathCreateCopyByStrokingPath([path CGPath], NULL, self.arcWidth, kCGLineCapButt, kCGLineJoinRound, 1.0);
UIBezierPath* newPath = [[UIBezierPath alloc] init];
[newPath setCGPath:tmp];