skip to Main Content

I draw a 10 point width path, but when I try to detect touches in it, UIBezierPath contains: method does only detect touches that happen at 1 pixel line and not 10 pixel line.

    let path = UIBezierPath()
    path.move(to: start)
    path.addLine(to: end)
    path.usesEvenOddFillRule = true
    path.lineWidth = 10
    path.lineCapStyle = .round
    path.stroke()
    path.close()

    let shapeLayer = CAShapeLayer()
    shapeLayer.path = path.cgPath
    shapeLayer.strokeColor = lineColor.cgColor
    shapeLayer.lineWidth = path.lineWidth

Here is a method to detect touch:

@objc func didTapOnView(_ recognizer: UIGestureRecognizer) {
    let tapLocation:CGPoint = recognizer.location(in: self)
    self.hitTest(tapLocation: CGPoint(x: tapLocation.x.rounded(), y: tapLocation.y.rounded()))
}

private func hitTest(tapLocation: CGPoint){

    let path: UIBezierPath = path0

    print("Touch location is: (tapLocation)")

    if path.contains(tapLocation){
       print("inside")
    } else{
        print("outside")
    }
}

I’m only getting touches that happen in exact 1 pixel line and not on 10 pixel line. If I don’t do rounding, no touches get detected at all.

2

Answers


  1. It seems like your path is only a line. For a line, contains only returns true for points that are on that line. It does not consider the stroke width. Of course, it is almost impossible to tap exactly on a line, which is why you don’t detect any touches at all, unless you round the touch location.

    You can get a CGPath representing the stroked area of a UIBezierPath using copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform:). Then you can use contains to check whether a point is inside this area.

    let path: UIBezierPath = path0.cgPath.copy(
        strokingWithWidth: 10,
        lineCap: .butt,
        lineJoin: .miter,
        miterLimit: 10
    )
    
    if path.contains(somePoint, using: .evenOdd) { ... }
    
    Login or Signup to reply.
  2. The Problem:

    containsPoint: determines if a given point precisely is on the 1-pixel-wide line route in mathematics.
    Due to restrictions on touch sensing and fingertip size, touches on a physical device are limited to a specific region rather than a single spot.

    Resolutions:

    Expand the Hit Area:

    Make the unseen path bigger: Encircle your real line with an invisible UIBezierPath that has a slightly larger stroke. This unseen route serves as a touch detection hitbox.

    let linePath = UIBezierPath()
    // Draw your actual line here
    
    // Create a wider invisible path for touch detection
    let touchPath = UIBezierPath(rect: linePath.bounds.insetBy(x: -hitAreaWidth, y: -hitAreaWidth))
    
    // Check touch events against the wider path
    if touchPath.contains(touchPoint) {
        // Handle touch within the thicker line area
    }
    

    Customized Hit Test:

    Create a unique hit test method that takes the touch position and line width into account:

    func isTouchWithinLine(touchPoint: CGPoint, linePath: UIBezierPath, lineWidth: CGFloat) -> Bool {
    // Calculate the line's bounding rectangle considering line width
    let lineRect = linePath.bounds.insetBy(x: -lineWidth / 2, y: -lineWidth / 2)
    
    // Check if the touch point is within the inflated rectangle with some tolerance 
    let buffer: CGFloat = 5 // Adjust tolerance as needed
    return lineRect.insetBy(x: -buffer, y: -buffer).contains(touchPoint)
    

    }

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search