> For the complete documentation index, see [llms.txt](https://lochiwei.gitbook.io/ios/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lochiwei.gitbook.io/ios/master/todo/tutorials/neumorphism/button-design/iconcard.md).

# IconCard 📦

{% hint style="info" %}
如果遮罩圖與要剪的紙一樣大，用 [SystemImage 📦](/ios/master/todo/custom-types/system-image.md)  就可以。\
如果遮罩圖較小、要剪的紙較大，用 [IconCard 📦](/ios/master/todo/tutorials/neumorphism/button-design/iconcard.md)  。
{% endhint %}

下圖顯示 `img` (**IconCard**) 從套用 .**foregroundColor**(.black)、.**background**(Color.white)、.**compositingGroup**()、.**luminanceToAlpha**()，一直到 `paper` (紅色星點紙) 套用 .**mask**() 的整個過程。

這個過程後來被濃縮成一個 View extension：[.inverseMask() 🌀](/ios/custom/ext/view/.inversemask.md)

![左上角是 IconCard 的原圖 (img)](/files/-MHqEOJ3H-uGDtkVr0eR)

{% tabs %}
{% tab title="📦  IconCard" %}

`````swift
import SwiftUI
import Workaround   // for `SystemImage`

/// # 📦 IconCard
/// - icon: SF Symbol 名稱。
/// - size: ⚠️ 注意：這是圖示大小，不是整張卡的大小‼️
/// ## Example
/// ````
/// IconCard(icon: "heart.fill", size: 32)
/// ````
public struct IconCard: View {
    
    // properties
    let iconSize: CGFloat   // icon size
    let icon    : String    // icon name
    
    /// public init
    public init(_ icon: String, size: CGFloat = 32) {
        self.icon     = icon
        self.iconSize = size
    }
    
    // body
    public var body: some View {
        
        // 1. 選用 ZStack，讓物件可以在上下層間輕鬆堆疊。
        // ⭐️ 先不限制 ZStack 的大小，它的大小由外部環境決定。
        ZStack {
            
            // 2. 先準備一張「透明紙」：這裡有兩個用意 ⭐️ 
            //    1. 用 Rectangle 來定義此「元件的大小」(否則會以下面的 SystemImage 為準)。
            //    2. 先指定 fg = .clear，將來如果整個元件再被設定 .foreground() 時，
            //       - Rectangle 的 fg 還是 .clear (不會被 overridden‼️)
            //       - SystemImage 的 fg 因為還沒被設定過，所以就可以被改寫。
            Rectangle()
                .foregroundColor(.clear)             // ⭐️ 指定「透明色」！
            
            // 3. 然後在上面畫圖示，並設定圖示大小。
            // ⭐️ 注意：雖然沒有指定前景色，但在深色模式下，會用「白色」表示。
            SystemImage(icon)
                .frame(width: iconSize, height: iconSize)
        } // container (ZStack)
        
    } // body
}
`````

{% endtab %}

{% tab title="📦  SystemImage" %}

`````swift
import SwiftUI
import Workaround   // for `SystemImage`

/// # 📦 IconCard
/// - icon: SF Symbol 名稱。
/// - size: ⚠️ 注意：這是圖示大小，不是整張卡的大小‼️
/// ## Example
/// ````
/// IconCard(icon: "heart.fill", size: 32)
/// ````
public struct IconCard: View {
    
    // properties
    let iconSize: CGFloat   // icon size
    let icon    : String    // icon name
    
    /// public init
    public init(_ icon: String, size: CGFloat = 32) {
        self.icon     = icon
        self.iconSize = size
    }
    
    // body
    public var body: some View {
        
        // 1. 選用 ZStack，讓物件可以在上下層間輕鬆堆疊。
        // ⭐️ 先不限制 ZStack 的大小，它的大小由外部環境決定。
        ZStack {
            
            // 2. 先準備一張「透明紙」：這裡有兩個用意 ⭐️ 
            //    1. 用 Rectangle 來定義此「元件的大小」(否則會以下面的 SystemImage 為準)。
            //    2. 先指定 fg = .clear，將來如果整個元件再被設定 .foreground() 時，
            //       - Rectangle 的 fg 還是 .clear (不會被 overridden‼️)
            //       - SystemImage 的 fg 因為還沒被設定過，所以就可以被改寫。
            Rectangle()
                .foregroundColor(.clear)             // ⭐️ 指定「透明色」！
            
            // 3. 然後在上面畫圖示，並設定圖示大小。
            // ⭐️ 注意：雖然沒有指定前景色，但在深色模式下，會用「白色」表示。
            SystemImage(icon)
                .frame(width: iconSize, height: iconSize)
        } // container (ZStack)
        
    } // body
}
`````

{% endtab %}

{% tab title="🌀  View\.label()" %}

```swift
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)
    }
}
```

{% endtab %}

{% tab title="範例" %}

```swift
import SwiftUI
import PlaygroundSupport

let shad = Color.black.opacity(0.8)
let clear = Image("transparent")
let paper = Image("red-stars").resizable().scaledToFit()
    .frame(maxWidth: 280, maxHeight: 200)

struct ContentView: View {
    
    // 0. 設定原圖、圖示大小、整個元件大小。
    let mask: some View = IconCard("heart.fill", size: 100)
        .frame(width: 150, height: 150)
    
    // 1. 套用 .foregroundColor()，前景塗黑。
    //    套用 .luminanceToAlph() 後，alpha = 0，變「透明」，等於「前景被挖掉」。
    // ⭐️ 注意：此指令會套用到「所有圖層」，但不會 override 已經有 fgColor 的圖層。
    var v1: some View { mask.foregroundColor(.black) }
    
    // 2. 背景塗白
    //    套用 .luminanceToAlph() 後，alpha = 1，所以「背景會保留下來」。
    var v2: some View { v1.background(Color.white) }   // opacity = 1 (show)
    
    // 3. 合併所有圖層
    var v3: some View { v2.compositingGroup() }
    
    // 4. 再將亮度調為 alpha
    var v4: some View { v3.luminanceToAlpha() }
    
    var v5: some View {
        ZStack {
            // 紅色星點紙
            paper
                .mask(v4)  // ⭐️ == paper.inverseMask(mask)
                .shadow(color: shad, radius: 4, x: 4, y: 4)
            
            // ⭐️ 畫原圖外框，但不顯示原圖。
            mask
                .hidden()
                .border(Color.black)
        } // ZStack
            .background(Gradient.down(.yellow, .green))
    }
    
    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                mask.label("img")
                v1   .label("fg = black")
                v2   .label("bg = white")
            }
            
            HStack(alignment: .top) {
                v4
                    .background(clear)    // ⭐️ 底下鋪「透明斜格紙」
                    .clipped()            // ⭐️ 裁掉超出 v4 的部分
                    .label("亮度轉 alpha")
                v5.label("paper.inverseMask(img)")
            }
            
        } // container (HStack)
            .padding()
            .background(Color.gray)
        
    } // body
}

PlaygroundPage.current.setLiveView(ContentView())
```

{% endtab %}

{% tab title="Second Tab" %}

{% endtab %}
{% endtabs %}
