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 the opposite affect: preventing anything outside the first circle from being visible.
For UIView
the answer is here: iOS invert mask in drawRect
However, trying to implement this in SwiftUI without UIView
eludes me. I tried implementing an InvertedShape with I could then use as a mask:
struct InvertedShape<OriginalType: Shape>: Shape {
let originalShape: OriginalType
func path(in rect: CGRect) -> Path {
let mutableOriginal = originalShape.path(in: rect).cgPath.mutableCopy()!
mutableOriginal.addPath(Path(rect).cgPath)
return Path(mutableOriginal)
.evenOddFillRule()
}
}
Unfortunately, SwiftUI paths do not have addPath(Path)
(because they are immutable) or evenOddFillRule()
. You can access the path's CGPath and make a mutable copy and then add the two paths, however, evenOddFillRule
needs to be set on the CGLayer
, not the CGPath
. So unless I can get to the CGLayer, I'm unsure how to proceed.
This is Swift 5.
Here is a demo of possible approach of creating inverted mask, by SwiftUI only, (on example to make a hole in view)
func HoleShapeMask(in rect: CGRect) -> Path {
var shape = Rectangle().path(in: rect)
shape.addPath(Circle().path(in: rect))
return shape
}
struct TestInvertedMask: View {
let rect = CGRect(x: 0, y: 0, width: 300, height: 100)
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(width: rect.width, height: rect.height)
.mask(HoleShapeMask(in: rect).fill(style: FillStyle(eoFill: true)))
}
}
Here's another way to do it, which is more Swiftly.
The trick is to use:
YourMaskView()
.compositingGroup()
.luminanceToAlpha()
maskedView.mask(YourMaskView())
Just create your mask with Black and White shapes, black will be transparent, white opaque, anything in between is going to be semi-transparent.
.compositingView()
, similar to .drawingGroup()
, rasterises the view (converts it to a bitmap texture). By the way, this also happens when you .blur
or do any other pixel-level operations.
.luminanceToAlpha()
takes the RGB luminance levels (I guess by averaging the RGB values), and maps them to the Alpha (opacity) channel of the bitmap.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With