I have a rectangle that I'm trying to do a reverse mask on, removing a variable number of other rectangles from the original shape.
If I were just trying to remove one rectangle, this would be easy, using an evenOdd fill. However, once I have multiple rectangles, the intersections of those rectangles become filled again. See the below example:
In the above example, I ideally, I wouldn't see the small red squares, which are the intersections of the 3 shapes overlayed onto the red background. In other words, the ideal is the inverse of this:
Is there any way I can eliminate the extra crossings in my Path
so that the entire area "covered" by my Shape
will get reverse-masked out?
Reproducible code to get to the first image:
struct MyShape: Shape {
func path(in rect: CGRect) -> Path {
var p = Path()
p.addRect(rect)
p.addRect(.init(origin: .init(x: 60, y: 60),
size: .init(width: 100, height: 100)))
p.addRect(.init(origin: .init(x: 120, y: 120),
size: .init(width: 100, height: 100)))
p.addRect(.init(origin: .zero,
size: .init(width: 100, height: 100)))
return p
}
}
struct ContentView: View {
var body: some View {
ZStack {
Color.red
.mask(
MyShape()
.fill(style: .init(eoFill: true))
)
}
.frame(width: 400, height: 300)
}
}
Note: I'm on macOS
, but have a deployment target of 11.3, so a solution using CGPath
's intersection
won't work.
Also worth noting that in my basic example, I could hard code the path by hand, but assume that the overlayed rectangles are coming in dynamically, so hardcoding a path also isn't workable.
Rather than use your shape directly as a mask, you can create a "reverse mask" using .blendMode(.destinationOut)
to "cut out" a shape from a Rectangle
, then the result is used as the mask:
struct MyShape: Shape {
func path(in rect: CGRect) -> Path {
var p = Path()
p.addRect(CGRect(x: 60, y: 60, width: 100, height: 100))
p.addRect(CGRect(x: 120, y: 120, width: 100, height: 100))
p.addRect(CGRect(x: 0, y: 0, width: 100, height: 100))
return p
}
}
struct ContentView: View {
var body: some View {
ZStack {
Color.yellow
ZStack {
Color.red.mask {
// Mask is Rectangle with MyShape removed
Rectangle()
.overlay() {
MyShape()
.blendMode(.destinationOut)
}
}
.frame(width: 400, height: 300)
}
}
}
}
A full (and excellent) explanation as to how this all works can be found here: