检测厚 UIBezierPath 上的触摸
Detecting touch on a thick UIBezierPath
在我的应用程序的绘图部分,用户应该能够 select 他们创建的路径。 UIBezierPath
有一个方便的方法 containsPoint:
在大多数情况下效果很好,但是当绘制的线接近直线而没有曲线时,它会非常糟糕。让你的手指打直线是很难的。
似乎该方法只检查路径上的一条非常窄的线,即使将路径的 lineWidth
属性 设置为较大的值也不会影响 [=14] 的结果=] 测试。
我看过类似的问题,比如这个:Detect touch on UIBezierPath stroke, not fill 但无法修改解决方案以查看 "an area around the path",它似乎只能查看中心的细线
编辑
我想出的一个有效但密集的解决方案是将路径转换为 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 Whosebug
// 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 pt stroke 因为它更接近 Apple 建议的最小触摸目标尺寸 44x44 像素。
CGPathRef pathCopy = CGPathCreateCopyByStrokingPath(originalPath.CGPath, nil, 40, kCGLineCapButt, kCGLineJoinMiter, 1);
UIBezierPath *fatOne = [UIBezierPath bezierPathWithCGPath:pathCopy];
if ([fatOne containsPoint:p]){
// match found
}
使用'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 )
制作原始路径的描边路径。并使用原始路径和描边路径进行测试。
更新swift3
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 )
objective 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];
在我的应用程序的绘图部分,用户应该能够 select 他们创建的路径。 UIBezierPath
有一个方便的方法 containsPoint:
在大多数情况下效果很好,但是当绘制的线接近直线而没有曲线时,它会非常糟糕。让你的手指打直线是很难的。
似乎该方法只检查路径上的一条非常窄的线,即使将路径的 lineWidth
属性 设置为较大的值也不会影响 [=14] 的结果=] 测试。
我看过类似的问题,比如这个:Detect touch on UIBezierPath stroke, not fill 但无法修改解决方案以查看 "an area around the path",它似乎只能查看中心的细线
编辑
我想出的一个有效但密集的解决方案是将路径转换为 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 Whosebug
// 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 pt stroke 因为它更接近 Apple 建议的最小触摸目标尺寸 44x44 像素。
CGPathRef pathCopy = CGPathCreateCopyByStrokingPath(originalPath.CGPath, nil, 40, kCGLineCapButt, kCGLineJoinMiter, 1);
UIBezierPath *fatOne = [UIBezierPath bezierPathWithCGPath:pathCopy];
if ([fatOne containsPoint:p]){
// match found
}
使用'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 )
制作原始路径的描边路径。并使用原始路径和描边路径进行测试。
更新swift3
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 )
objective 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];