👔view.inverseMask()
- blend mode - 用 - .destinationOut可以達到類似的效果。 ⭐️
import SwiftUI
extension View {
    // view.inverseMask(_:)
    public func inverseMask<M: View>(_ mask: M) -> some View {
        // exchange foreground and background
        let inversed = mask
            .foregroundColor(.black)  // opacity = 0 (hide)
            .background(Color.white)  // opacity = 1 (show)
            .compositingGroup()       // ⭐️ composite all layers
            // ⭐️ dark   -> transparent  (opacity = 0)
            //    bright -> opaque black (opacity = 1)
            //    有點「負片」的感覺。
            .luminanceToAlpha()       // ⭐️ turn luminance into alpha (opacity)
        return self.mask(inversed)
    }
}
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
    var body: some View {
        // light bulb (resizable image)
        let lightbulb = Image(systemName: "lightbulb")
            .resizable().scaledToFit().padding(24)
        // rounded rect (shape)
        let roundedRect = RoundedRectangle(cornerRadius: 20)
        // rounded gradient border
        let border = roundedRect
            .stroke(Neu.gradient.diagonalBorderDark, lineWidth: 2)
        // card container
        return ZStack {
            // card background
            Neu.color.grayBackground
            // card
            Neu.gradient.horizontalDark
                .inverseMask(lightbulb)   // ⭐️ inverse mask
                .shadow(color: Color.black.opacity(0.6), radius: 4, x: 4, y: 4)
                .frame(width: 150, height: 200)
                .clipShape(roundedRect)
                .overlay(border)
                .shadow(color: Color.white.opacity(0.9), radius: 18, x: -18, y: -18)
                .shadow(color: Color.black.opacity(0.3), radius: 14, x: 14, y: 14)
        }
    }
}
PlaygroundPage.current.setLiveView(ContentView())
- How to Create a Neumorphic Design With SwiftUI - RayWenderlich 
- Inverted mask swiftui with system image - - destinationView mask.blendMode(.destinationOut) // source view (mask)
細節說明
在 .inverseMask() 中用到的 .foregroundColor(.black) 和 .backgroud(Color.white),對 Image 來說沒有任何作用,如下圖所示:

但 .luminanceToAlpha() 會將原圖中「黑色的部分」轉為「透明」(opacity = 0),而「白色的部分」則轉為「全黑」(opacity = 1),這時的圖有點像相片的「底片」(負片)。
套用 .mask() 的時候,opacity = 1 的會「保留原來的色彩」,opacity = 0 的部分則轉為「透明」。這就是爲什麼原圖小狗的眼睛四周比較黑,會造成最後的圖在小狗眼睛四周有些透明。
import SwiftUI
import PlaygroundSupport
extension View {
    @ViewBuilder
    func label(_ text: String) -> some View {
        VStack(alignment: .leading) {
            self.border(Color.black)
            Text(text).padding(.leading).padding(.bottom, 10)
        }.border(Color.black)
    }
}
let shad = Color.black.opacity(0.8)
let numberPaper = Image("numbers")).resizable().scaledToFill()
    .rotationEffect(.degrees(90)).frame(maxWidth: 280)
struct ContentView: View {
    
    let puppy: some View = Image("puppy")).resizable().scaledToFit().frame(width: 150)
    
    var v1: some View { puppy.foregroundColor(.black) } // opacity = 0 (hide)
    var v2: some View { v1.background(Color.white) }   // opacity = 1 (show)
    var v3: some View { v2.compositingGroup() }        // ⭐️ composite all layers
    var v4: some View { v3.luminanceToAlpha() }        // ⭐️ turn luminance into alpha
    
    var v5: some View {
        ZStack {
            numberPaper.mask(v4).shadow(color: shad, radius: 4, x: 4, y: 4) 
            puppy.hidden().border(Color.black)
        }.background(Gradient.down(.yellow, .green))
    }
    
    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                puppy.label("image")
                v1   .label("fg = black")
                v2   .label("bg = white")
            }
            
            HStack(alignment: .top) {
                v4.background(Color.white).label("亮度轉 alpha")
                v5.label("mask(image)")
            }
            
        } // container (HStack)
            .padding()
            .background(Color.gray)
        
    } // body
}
PlaygroundPage.current.setLiveView(ContentView())加上黑色前景、白色背景都有效,這時會有兩個圖層,套用 .compositingGroup() 會合併圖層。合併後,再套用 .luminanceToAlpha(),mask 原圖前景的部分 (也就是原來圖示的部分) 轉為透明,這時再套用 .mask() 就可以「挖掉」原圖的這個部分,這也就是為什麼 .inverseMask() 會有效的原因。

import SwiftUI
import PlaygroundSupport
let shad = Color.black.opacity(0.8)
let clear = Image(uiImage: #imageLiteral(resourceName: "IMG_0328.PNG"))
let paper = Image(uiImage: #imageLiteral(resourceName: "red stars.PNG"))
    .resizable().scaledToFit()
    .frame(maxWidth: 280, maxHeight: 200)
extension View {
    @ViewBuilder
    func label(_ text: String) -> some View {
        VStack(alignment: .leading) {
            self.border(Color.black)
            Text(text).padding(.leading).padding(.bottom, 10)
        }.border(Color.black)
    }
}
struct ContentView: View {
    
    let mask: some View = Image(systemName: "cloud.drizzle.fill").resizable().scaledToFit().frame(width: 150)
    
    var v1: some View { mask.foregroundColor(.black) } // opacity = 0 (hide)
    var v2: some View { v1.background(Color.white) }   // opacity = 1 (show)
    var v3: some View { v2.compositingGroup() }        // ⭐️ composite all layers
    var v4: some View { v3.luminanceToAlpha() }        // ⭐️ turn luminance into alpha
    
    var v5: some View {
        ZStack {
            paper.mask(v4).shadow(color: shad, radius: 4, x: 4, y: 4) 
            mask.hidden().border(Color.black)
        }.background(Gradient.down(.yellow, .green))
    }
    
    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                mask.label("image")
                v1   .label("fg = black")
                v2   .label("bg = white")
            }
            
            HStack(alignment: .top) {
                v4.background(clear).clipped().label("亮度轉 alpha")
                v5.label("mask(image)")
            }
            
        } // container (HStack)
            .padding()
            .background(Color.gray)
        
    } // body
}
PlaygroundPage.current.setLiveView(ContentView())Last updated
Was this helpful?