# 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 %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lochiwei.gitbook.io/ios/master/todo/tutorials/neumorphism/button-design/iconcard.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
