# Configuring Views

{% hint style="info" %}
在設計 View 的時候，可以利用不同的方法，來達到同一個目的，如：「利用自製元件、View extension、ViewModifier、ViewBuilder」等。
{% endhint %}

{% tabs %}
{% tab title="📗 參考" %}

* [ ] [Encapsulating SwiftUI view styles](https://www.swiftbysundell.com/articles/encapsulating-swiftui-view-styles/) - Sundell
  {% endtab %}

{% tab title="📘 手冊" %}

* [SwiftUI](https://developer.apple.com/documentation/swiftui) ⟩ [View and Controls](https://developer.apple.com/documentation/swiftui/views-and-controls) ⟩ [View](https://developer.apple.com/documentation/swiftui/view) ⟩ [Styling](https://developer.apple.com/documentation/swiftui/styling)&#x20;
* SwiftUI ⟩
  * [View Modifier](/ios/swiftui/view/modifier/viewmodifier.md)
  * [View Builder](/ios/swiftui/view/view-builder.md)
    {% endtab %}
    {% endtabs %}

## 範例

下面我們展示如何用不同的方式來寫「**在 TextField 前加一個圖示**」這個動作：&#x20;

<div align="center"><img src="/files/-MH6p-fTeDI-CsWC9Jai" alt="在 TextField 前加一個圖示"></div>

1. 直接用 **HStack**&#x20;
2. 寫成一個「**元件」**：`IconTextField`
3. 用 **View extension：**`view.prefix(icon:)`&#x20;
4. 用 **ViewModifier**：`PrefixIcon`
5. 用 **ViewBuilder：**`IconTextFieldView`

{% tabs %}
{% tab title="1. main" %}

```swift
import SwiftUI
import PlaygroundSupport

// 表單
struct SignUpForm: View {
    
    @State private var username = ""
    @State private var email    = ""
    @State private var phone    = ""
    @State private var tel      = ""
    @State private var calendar = ""
    
    var body: some View {
        Form {
            // 表頭
            Text("表單").font(.headline)
                
            // ⭐️ 1. HStack
            HStack {
                Image(systemName: "person.circle.fill")
                TextField("姓名", text: $username)
                    .validate(username) { $0.count > 2 }      // ⭐️ 使用 Validation
            }
            
            // ⭐️ 2. 使用元件 `IconTextField`
            IconTextField(
                icon : "envelope.circle.fill", 
                title: "信箱", 
                text : $email
            )
            
            // ⭐️ 3. 使用 `View` extension
            TextField("0800-000-123", text: $phone)
                .textFieldStyle(RoundedBorderTextFieldStyle()) // ⭐️ 使用 TextFieldStyle
                .validate(phone) { $0.count > 2 }
                .prefix(icon: "phone.circle.fill")             // ⭐️ View extension
            
            // ⭐️ 4. 使用 `ViewModifier`
            // ⚠️ 注意：
            //    此處先用 PrefixIcon 再用 Validation，
            //    所以 Validation 的紅色外框會連「圖示」都框起來！
            TextField("(03) 000-1234", text: $tel)
                .modifier(PrefixIcon(name: "phone.circle.fill"))// ⭐️ ViewModifier
                .validate(tel) { $0.count > 1 }
            
            // ⭐️ 5. ViewBuilder
            IconTextFieldView(iconName: "calendar.circle.fill") {
                TextField("2020/09/14", text: $calendar)
            }
            
            Button(
                action: {  },
                label : { Text("確定") }
            )
            
        }.accentColor(.purple)
    }
}

// live view
struct ContentView: View {
    var body: some View {
        VStack {
            SignUpForm()
        }
    }
}

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

{% endtab %}

{% tab title="2. IconTextField" %}

```swift
// 2. 元件
struct IconTextField: View {
    // local properties
    var icon : String             // icon name for SF Symbols
    var title: String             // placeholder text for TextField
    // property from parent view
    @Binding var text: String     // text in TextField
    // body of this component
    var body: some View {
        HStack {
            Image(systemName: icon)
            TextField(title, text: $text)
                .textFieldStyle(RoundedBorderTextFieldStyle())  // ⭐️ TextFieldStyle
        }
    }
}
```

{% endtab %}

{% tab title="3. View ext." %}

```swift
// 3. `View` extension
extension View {
    // view.prefix(icon: "phone.circle.fill")
    func prefix(icon name: String) -> some View {
        HStack {
            Image(systemName: name)
            self
        }
    }
}
```

{% endtab %}

{% tab title="4. PrefixIcon" %}

```swift
// 4. ViewModifier
struct PrefixIcon: ViewModifier {
    var name: String                           // icon name for SF Symbols
    // ⭐️ ViewModifier's only requirement
    func body(content: Content) -> some View {
        HStack {
            Image(systemName: name)
            content
        }
    }
}
```

{% endtab %}

{% tab title="5. IconTextFieldView" %}

```swift
struct IconTextFieldView<Content: View>: View {
    
    // properties
    let iconName: String
    let content: Content
    
    // IconTextFieldView(iconName: "...") { ... }
    init(
        iconName            : String, 
        @ViewBuilder content: () -> Content  // ⭐️ ViewBuilder
    ) {
        self.iconName = iconName
        self.content = content() 
    }
    
    // conforms to `View` protocol
    var body: some View {
        // ⭐️ 在 `content` 前面放一個圖示。
        HStack {
            Image(systemName: iconName)
            content
        }
    }
}
```

{% endtab %}

{% tab title="Validation" %}

```swift
// ⭐️ Validation (ViewModifier)
struct Validation<Value>: ViewModifier {
    
    var value   : Value            // 要被檢查的值
    var validate: (Value) -> Bool  // 負責檢查的函數
    
    func body(content: Content) -> some View {
        
        // 外框的設定
        let color: Color = validate(value) ? .clear : .pink
        let rounded = RoundedRectangle(cornerRadius: 6)  // 1. 外型：圓角
            .stroke(color)                               // 2. 顏色：透明(通過檢查)
            //                                           //         紅色(沒通過)
            // ⭐️ 「負的 padding」可讓 child view 長得比 parent view 還大
            .padding(-4)                                 // 3. 位置：在外面
        
        // draw outline
        return content
            .overlay(rounded)     // ⭐️ 設定圓角外框
            .animation(.default)  // ⭐️ 設定動畫
    }
}

// Validation helper
extension View {
    // view.validate(value) { ... }
    func validate<Value>(
        _  value: Value, 
        validate: @escaping (Value) -> Bool
    ) -> some View {
        self.modifier(Validation(value: value, validate: validate))
    }
}
```

{% endtab %}
{% endtabs %}

## 參考資料

* [Configuring SwiftUI Views](https://www.swiftbysundell.com/articles/configuring-swiftui-views/) - Swift by Sundell
* [View Modifier](/ios/swiftui/view/modifier/viewmodifier.md) - 🍎
* [View Builder](/ios/swiftui/view/view-builder.md) - 🍎


---

# 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/swiftui/view/configuring-views.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.
