Swift Combine and reverse UIBezierPaths

283 Views Asked by At

The problem I am facing is reversing subpaths. Take this example:

let circlePaths: [UIBezierPath] = ...
let rectanglePath: UIBezierPath = ... // a rectangle

let totalPath: UIBezierPath = .init()
for path in circlePaths {
  totalPath.append(path)
}
rectanglePath.append(totalPath)

It should look like this:

circle image

Now ideally I want to cut out all the circles using

bezierPath.append(totalPath.reversing())

However the effect is not as expected. I expect the two circles to make up a path and this one is reversed, however in reality both circle paths are reversed, which causes the intersection to be part of the path (reversing() twice has no effect). I'd like to combine the circle paths into one with the intersection not being present but as part of the path. I want the smaller circle to "extend" the larger circle as a path.

Any idea how I would do it?

Edit 1: Here is an image how the resulting path should look like.

enter image description here

1

There are 1 best solutions below

0
DonMag On

If you need to actually create a single path as the combination / union of your multiple paths, you may want to look at one of the libraries that are out there.

However, if you only need that visual output, this might be a usable approach.

  • Create 3 paths -- outer rect, large circle, small circle.
  • Stroke and Fill the outer rect path
  • Stroke both circle paths
  • Fill both circle paths

An example UIView class:

class FakeUnionUIBezierPaths : UIView {
    override func draw(_ rect: CGRect) {
        
        // yellow line width
        let lWidth: CGFloat = 8
        
        // paths are stroked on the center, so
        // use one-half the line width to adjust ediges
        let hWidth: CGFloat = lWidth * 0.5
        
        // border / outer rect, inset by one-half line width
        let dRect: CGRect = rect.insetBy(dx: hWidth, dy: hWidth)
        
        // first circle
        //  full-height
        //  aligned to left edge
        var c1Rect: CGRect = dRect
        c1Rect.size.width = c1Rect.height
        
        // second circle
        //  half-height
        //  aligned to right edge
        var c2Rect: CGRect = dRect
        c2Rect.size.height *= 0.5
        c2Rect.size.width = c2Rect.height
        c2Rect.origin.x = dRect.width - c2Rect.width + hWidth
        c2Rect.origin.y = dRect.height * 0.25
        
        let pRect: UIBezierPath = UIBezierPath(rect: dRect)
        let p1: UIBezierPath = UIBezierPath(ovalIn: c1Rect)
        let p2: UIBezierPath = UIBezierPath(ovalIn: c2Rect)
        
        UIColor.yellow.setStroke()
        UIColor.blue.setFill()

        // same line-width for all three paths
        [pRect, p1, p2].forEach { p in
            p.lineWidth = lWidth
        }
        
        // stroke and fill the border / outer rect
        pRect.stroke()
        pRect.fill()

        // stroke both circle paths
        p1.stroke()
        p2.stroke()
        
        // fill both circle paths
        p1.fill()
        p2.fill()
        
    }
}

Output:

enter image description here