2015-05-04 3 views
0

В разделе чертежа моего приложения пользователь должен иметь возможность выбрать путь, который они создали. UIBezierPath имеет удобный метод containsPoint:, который хорошо работает в большинстве случаев, но это очень плохо, когда линия нарисована близка к прямой, без кривых. Прикосновение пальца к прямой линии очень сложно.Обнаружение касания на толстом UIBezierPath

Похоже, что метод проверяет очень узкую линию вдоль пути, и даже значение свойства lineWidth пути к большему значению не влияет на результаты теста containsPoint:.

Я рассмотрел аналогичные вопросы, например, этот: Detect touch on UIBezierPath stroke, not fill, но не смог изменить решение, чтобы посмотреть на «область вокруг пути», она только кажется, смотрит на тонкую линию в центре ее.

Редактировать

рабочая, но интенсивное решение, которое я придумал, чтобы преобразовать путь в UIImage и проверить цвет этого изображения в точке запрошенной. Если альфа-компонент больше нуля, путь пересекает прикосновение.

Это выглядит следующим образом:

// 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 минимальному размеру касания, равному 44х44 пикселям.

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

ответ

2

Используйте '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) 

Делает погладил путь первоначального пути. И тест попал, используя как оригинальный, так и поглаженный путь.

+0

Отлично! Это дает мне те же результаты, что и сложный метод, который я придумал, но примерно в 100-200 раз быстрее. – SaltyNuts

2

обновление скор 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) 

обновление для 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]; 
Смежные вопросы