I\'m trying to add a mask to two shapes such that the second shape masks out the first shape. If I do something like Circle().mask(Circle().offset(…))
, this has
Using a mask such as in the accepted answer is a good approach. Unfortunately, masks do not affect hit testing. Making a shape with a hole can be done in the following way.
extension Path {
var reversed: Path {
let reversedCGPath = UIBezierPath(cgPath: cgPath)
.reversing()
.cgPath
return Path(reversedCGPath)
}
}
struct ShapeWithHole: Shape {
func path(in rect: CGRect) -> Path {
var path = Rectangle().path(in: rect)
let hole = Circle().path(in: rect).reversed
path.addPath(hole)
return path
}
}
The trick is to reverse the path for the hole. Unfortunately Path
does not (yet) support reversing the path out-of-the-box, hence the extension (which uses UIBezierPath
). The shape can then be used for clipping and hit-testing purposes:
struct MaskedView: View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(width: 300, height: 100)
.clipShape(ShapeWithHole()) // clips or masks the view
.contentShape(ShapeWithHole()) // needed for hit-testing
}
}