swiftuishapes

How to combine shapes and return a single shape, so you have a single edge to trace?


I need to get only the outline of the union of four circles. That is, a shape with the shape's border.

SwiftUI has .union, but I can't use it within a shape.

struct LeafCloverShape: Shape {
    func path(in rect: CGRect) -> Path {
        let dimension = min(rect.width, rect.height)
        let r = dimension / 4
        let center = CGPoint(x: rect.midX, y: rect.midY)

        let centers = [
            CGPoint(x: center.x - r, y: center.y), // Left
            CGPoint(x: center.x + r, y: center.y), // Right
            CGPoint(x: center.x, y: center.y - r), // Top
            CGPoint(x: center.x, y: center.y + r)  // Bottom
        ]

        let cgPath = CGMutablePath()

        for c in centers {
            cgPath.addEllipse(in: CGRect(x: c.x - r, y: c.y - r, width: r * 2, height: r * 2))
        }

        return Path(cgPath)
    }
}

#Preview {
    LeafCloverShape()
        .stroke(.green, lineWidth: 4)
        .frame(width: 256, height: 256)
}

enter image description here


Solution

  • There is a union function on both CGPath (CGPath.union) and SwiftUI Path (Path.union). Instead of adding a subpath using addEllipse, call union instead.

    var cgPath: CGPath = CGMutablePath()
    
    for c in centers {
        cgPath = cgPath.union(CGPath(ellipseIn: CGRect(x: c.x - r, y: c.y - r, width: r * 2, height: r * 2), transform: nil))
    }
    
    return Path(cgPath)
    

    or work with SwiftUI Paths directly:

    return centers.map { c in 
        Path(ellipseIn: CGRect(x: c.x - r, y: c.y - r, width: r * 2, height: r * 2)) 
    }
    .reduce(Path()) { $0.union($1) }