Search code examples
swiftuibezierpathcashapelayer

Swift iOS: Determine if bezier path intersects with UIView frame?


I’ve created a custom UIView that draws a UIBezierPath, assigns it to a CAShapeLayer, and then masks it to the UIView’s root layer. Now what I’m trying to do is determine if a different UIView is intersecting (or overlapping) any part of the bezier path in that custom view, using the following code:

    let customView = … // define UICustomView

    let purpleView = UIView(frame: CGRect(x: 30, y: 500, width: 100, height: 50))
    purpleView.backgroundColor = UIColor.purple
    self.view.addSubview(purpleView)
    
    if purpleView.frame.intersects((customView.shapeLayer.path?.boundingBoxOfPath)!) == true { 
        print("Overlapping")
    } else {
        print("Not overlapping")
    }

The only problem is that it only returns true if the purpleView is at the top left of the partial square. The placement below returns false:

(I want it to return true if it’s overlapping any of the green, and false otherwise. I’ve only drawn 3 sides of the square and intentionally left the fill empty)

enter image description here

The following is the code for the custom view

class CustomView : UIView {

private var _path : UIBezierPath!
public let shapeLayer = CAShapeLayer()

override init(frame: CGRect) {
    super.init(frame: frame)
    self._setup()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self._setup()
}

private func _setup() {
    self._createSquare()
}

private func _createSquare() { 
    self._path = UIBezierPath()
    self._path.move(to: CGPoint(x: 0.0, y: 0.0))
    self._path.addLine(to: CGPoint(x: 0.0, y: self.frame.size.height))
    self._path.addLine(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height))
    self._path.addLine(to: CGPoint(x: self.frame.size.width, y: 0.0))
    //self._path.close()
    
    
    
    self.shapeLayer.path = self._path.cgPath
    self.shapeLayer.lineWidth = 10
    self.shapeLayer.strokeColor = UIColor.green.cgColor 
    self.backgroundColor = UIColor.green
    self.shapeLayer.fillColor = nil 
    self.layer.mask = self.shapeLayer   
}
}

Is there a way to make this happen? I realize using the frame.intersects() function might not be the right approach.


Solution

  • You are mixing up coordinate spaces. boundBoxOfPath is in CustomView's coordinate space, whereas purpleView.frame is in the coordinate space of self.view. You should only compare paths when they are in the same coordinate space. The various convert methods in UICoordinateSpace can be used to convert between coordinate spaces.

    if let pathBounds = customView.shapeLayer.path?.boundingBoxOfPath,
       self.view.convert(purpleView.frame, to: customView).intersects(pathBounds) {
        print("Overlapping")
    } else {
        print("Not overlapping")
    }
    

    Note that this will check if the two rectangles overlap. It will return true for cases like this too:

    enter image description here

    The green "rectangle" completely covers the purple rectangle, and that is also a kind of intersection.

    If you just want to check if the purple rectangle intersects the green lines, you can create a new path from the green path, such that, when filled with green, the result is the same as if you stroked the green path. See copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform:).

    Then, you create a rectangular path from purpleView.frame, and check if the new path and the rectangular path intersects, using CGPath.intersects.

    let purpleViewPath = CGPath(rect: self.view.convert(purpleView.frame, to: customView), transform: nil)
    if let path = customView.shapeLayer.path?.copy(
        strokingWithWidth: customView.shapeLayer.lineWidth,
    
        // these are the default values for a CGShapeLayer.
        // you can also try converting the values from the corresponding properties in CGShapeLayer
        lineCap: .butt,
        lineJoin: .miter,
        miterLimit: 10
    ),
       purpleViewPath.intersects(path, using: .evenOdd) {
        print("Overlapping")
    } else {
        print("Not overlapping")
    }