12

I have a UIView which is added as a subview to my view controller. I have drawn a bezier path on that view. My drawRect implementation is below

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    UIBezierPath *bpath = [UIBezierPath bezierPath];

    [bpath moveToPoint:CGPointMake(50, 50)];
    [bpath addLineToPoint:CGPointMake(100, 50)];
    [bpath addLineToPoint:CGPointMake(100, 100)];
    [bpath addLineToPoint:CGPointMake(50, 100)];
    [bpath closePath];

    CGContextAddPath(context, bpath.CGPath);
    CGContextSetStrokeColorWithColor(context,[UIColor blackColor].CGColor);
    CGContextSetLineWidth(context, 2.5);
    CGContextStrokePath(context);
    UIColor *fillColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.5 alpha:0.7];
    [fillColor setFill];
    [bpath fill];
}

I want detect tap inside this bezier path but not the point which is inside the UIView and outside the path. For example in this case if my touch coordinate is (10, 10), it should not be detected. I know about CGContextPathContainsPoint but it does not help when touch is inside the path. Is there a way to detect touch events inside bezier path?

blancos
  • 1,576
  • 2
  • 16
  • 38
  • Maybe you need to add `CGPathCloseSubpath`. I have updated my answer. Please check. – Avt Mar 27 '14 at 15:29
  • 1
    "detect tap inside this bezier path not inside the UIView" This bezier path is drawn inside the UIView, so how can a tap be inside it but not inside the UIView? – matt Mar 27 '14 at 15:54
  • @blancos - Thanks, just making sure we're all on the same page! :) – matt Mar 27 '14 at 16:13

4 Answers4

21

There is a function CGPathContainsPoint() it may be useful in your case.

Also be careful if you get gesture point from superview, the coordinate may not be correct with your test. You have a method to convertPoint from or to a particular view's coordinate system:

- (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view
- (CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view
foOg
  • 3,126
  • 3
  • 19
  • 18
9

Try UIBezierPath's method :

func contains(_ point: CGPoint) -> Bool

Returns a Boolean value indicating whether the area enclosed by the receiver contains the specified point.

Avt
  • 16,927
  • 4
  • 52
  • 72
9

Detecting tap inside a bezier path in swift :-

It's simple in latest swift ,follow these steps and you will get your UIBezierPath touch event.

Step 1 :- Initialize Tap Event on view where your UIBeizerPath Added.

///Catch layer by tap detection let tapRecognizer:UITapGestureRecognizer = UITapGestureRecognizer.init(target: self, action: #selector(YourClass.tapDetected(_:))) viewSlices.addGestureRecognizer(tapRecognizer)

Step 2 :- Make "tapDetected" method

  //MARK:- Hit TAP
public func tapDetected(tapRecognizer:UITapGestureRecognizer){
    let tapLocation:CGPoint = tapRecognizer.locationInView(viewSlices)
    self.hitTest(CGPointMake(tapLocation.x, tapLocation.y))


}

Step 3 :- Make "hitTest" final method

  public func hitTest(tapLocation:CGPoint){
        let path:UIBezierPath = yourPath
        if path.containsPoint(tapLocation){
            //tap detected do what ever you want ..;)
        }else{
             //ooops you taped on other position in view
        }
    }



Update: Swift 4

Step 1 :- Initialize Tap Event on view where your UIBeizerPath Added.

///Catch layer by tap detection
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(YourClass.tapDetected(tapRecognizer:)))
viewSlices.addGestureRecognizer(tapRecognizer)

Step 2 :- Make "tapDetected" method

public func tapDetected(tapRecognizer:UITapGestureRecognizer){
    let tapLocation:CGPoint = tapRecognizer.location(in: viewSlices)
    self.hitTest(tapLocation: CGPoint(x: tapLocation.x, y: tapLocation.y))
}

Step 3 :- Make "hitTest" final method

private func hitTest(tapLocation:CGPoint){
    let path:UIBezierPath = yourPath

    if path.contains(tapLocation){
        //tap detected do what ever you want ..;)
    }else{
        //ooops you taped on other position in view
    }
}
Diogo Souza
  • 370
  • 2
  • 12
Abhimanyu Daspan
  • 1,095
  • 16
  • 20
  • 1
    @AbhimanyuRathore - thank you for the explanation, and Diogo, thank you for the Swift 4 update! I got this functionality to work for the most part, but I'm having trouble with accuracy.. I've described the problem in detail here: https://stackoverflow.com/questions/55963229/why-is-the-hittest-tap-detection-within-a-bezier-path-producing-inaccurate-res and I'm wondering if either or both of you could comment... Any tips for troubleshooting the issue would be deeply appreciated. – Matvey May 03 '19 at 16:02
  • @DiogoSouza (tagging you as well, just in case.. thank you in advance) – Matvey May 03 '19 at 18:24
  • Dear @Matvey I Tried to give my explanation there on your question https://stackoverflow.com/questions/55963229/why-is-the-hittest-tap-detection-within-a-bezier-path-producing-inaccurate-res/55988774#55988774. thank you. – Abhimanyu Daspan May 05 '19 at 04:16
  • @Matvey Need to replace UIGraphicsGetCurrentContext with CAShapeLayer inside our view which we are using for tapDetection inside UIViewcontroller. – Abhimanyu Daspan May 05 '19 at 04:20
  • @Matvey StyleKit.drawA5 function require modification , there we need to replace UIGraphicsGetCurrentContext with CAShapeLayer for UIBezierPath for aquracy – Abhimanyu Daspan May 05 '19 at 06:30
1

A solution in Swift 3.1 (porting over the Apple recommended solution from here)

func containsPoint(_ point: CGPoint, path: UIBezierPath, inFillArea: Bool) -> Bool {

        UIGraphicsBeginImageContext(self.size)

        let context: CGContext? = UIGraphicsGetCurrentContext()
        let pathToTest = path.cgPath
        var isHit = false

        var mode: CGPathDrawingMode = CGPathDrawingMode.stroke

        if inFillArea {

            // check if UIBezierPath uses EO fill
            if path.usesEvenOddFillRule {
                mode = CGPathDrawingMode.eoFill
            } else {
                mode = CGPathDrawingMode.fill
            }
        } // else mode == stroke

        context?.saveGState()
        context?.addPath(pathToTest)

        isHit = (context?.pathContains(point, mode: mode))!
        context?.restoreGState()

        return isHit
        }
JaredH
  • 2,338
  • 1
  • 30
  • 40