DA Tips » #6

SwiftUI: How to Add an Inverted Mask

SwiftUI's mask(alignment:_:) method allows you to mask the view using the alpha channel of a given view. However, sometime you want to add an inverted mask. There are a few ways to achieve this depending on how your mask view is constructed.

Example of inverted mask

Example of inverted mask effect.

Use eoFill

If your mask is a shape, you can create another shape by adding a Rectangle path and applying a FillStyle(eoFill: true):

struct CircleInRectangle: Shape {
    let padding: CGFloat
    func path(in rect: CGRect) -> Path {
        var shape = Rectangle().path(in: rect)
        let paddedRect = rect.insetBy(dx: padding, dy: padding)
        shape.addPath(Circle().path(in: paddedRect))
        return shape
    }
}

struct ContentView: View {
    var body: some View {
        Image(uiImage: #imageLiteral(resourceName: "img.jpeg"))
            .resizable()
            .scaledToFill()
            .clipped()
            .frame(width: 400, height: 225)
            .background(.white)
            .mask(
                CircleInRectangle(padding: 24)
                    .fill(style: .init(eoFill: true))
            )
        )
    }
}

Use .luminanceToAlpha()

SwiftUI's .luminanceToAlpha() modifier creates a mask by making dark colors transparent and bright colors opaque.

struct ContentView: View {
    var body: some View {
        Image(uiImage: #imageLiteral(resourceName: "pattern.jpeg"))
            .resizable()
            .scaledToFill()
            .clipped()
            .frame(width: 400, height: 225)
            .background(.white)
            .mask(
                Circle()
                    .padding()
                    .foregroundColor(.black)
                    .background(.white)
                    .compositingGroup()
                    .luminanceToAlpha()
            )
    }
}

Use .blendMode(.destinationOut)

SwiftUI's .blendMode(.destinationOut) modifier allows you to erase any of the background that is covered by opaque source pixels.

struct ContentView: View {
    var body: some View {
        Image(uiImage: #imageLiteral(resourceName: "pattern.jpeg"))
            .resizable()
            .scaledToFill()
            .clipped()
            .frame(width: 400, height: 225)
            .background(.white)
            .mask(
                Color.black
                    .overlay(
                        Circle()
                            .padding()
                            .blendMode(.destinationOut)
                    )
                    .compositingGroup()
            )
    }
}