# 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](modifier/viewmodifier)
  * [View Builder](view-builder)
{% endtab %}
{% endtabs %}

## 範例

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

<div align="center"><img src="https://1830103165-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M5-JmwCZMKh_d7RfBaN%2F-MH6otfQGYmyhHG3ybrZ%2F-MH6p-fTeDI-CsWC9Jai%2FIMG_0260.jpeg?alt=media&#x26;token=b2a0adb5-64b8-488e-bf7d-4f73557cfb72" 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](modifier/viewmodifier) - 🍎
* [View Builder](view-builder) - 🍎

