# ViewBuilder

[SwiftUI](..) ⟩ [Views]() ⟩

{% hint style="success" %}
From <mark style="color:red;">**Xcode 12**</mark>, both <mark style="color:purple;">**switch**</mark> and <mark style="color:purple;">**if let**</mark> are supported in the **ViewBuilder**!
{% endhint %}

{% tabs %}
{% tab title="⭐️ 重點" %}
{% hint style="info" %}
You typically use [`ViewBuilder`](https://developer.apple.com/documentation/swiftui/viewbuilder) as a <mark style="color:red;">**parameter attribute**</mark> for <mark style="color:orange;">**child view-producing**</mark> <mark style="color:red;">**closure parameters**</mark>, allowing those closures to provide <mark style="color:orange;">**multiple child views**</mark>. 👉 SwiftUI ⟩ [View](https://developer.apple.com/documentation/swiftui/view) ⟩ [ViewBuilder](https://developer.apple.com/documentation/swiftui/viewbuilder)
{% endhint %}

{% hint style="info" %}
As for **ViewBuilders**, it is mainly used to create **custom container views**, which can also become a **reusable** view component.\
👉  [Understanding SwiftUI's ViewModifiers and ViewBuilders](https://vinsol.com/blog/2020/07/02/understanding-swiftuis-viewmodifiers-and-viewbuilders/)
{% endhint %}

{% hint style="info" %}
A good rule of thumb for me has to be to **use a modifier first**, and only use a **builder** when the code patterns really pulled strongly for that syntax. \
👉  [View Builders](https://netsplit.com/swiftui/view-builders/) - NetSplit.com
{% endhint %}

{% hint style="info" %}
_@**ViewBuilder**_ is one of the possible [function builders](../../swift/attributes/result-builders).\
👉  [Swift with Majid](https://swiftwithmajid.com/2019/12/18/the-power-of-viewbuilder-in-swiftui/)
{% endhint %}
{% endtab %}

{% tab title="📘 手冊" %}
* SwiftUI ⟩ [View](https://developer.apple.com/documentation/swiftui/view) ⟩ [ViewBuilder](https://developer.apple.com/documentation/swiftui/viewbuilder)
{% endtab %}

{% tab title="📗 參考" %}
* [x] [ViewBuilder & Extra argument in call 問題](https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E5%95%8F%E9%A1%8C%E8%A7%A3%E7%AD%94%E9%9B%86/viewbuilder-ambiguous-reference-to-member-buildblock-c7325ae5567f) - 彼得潘
*   [x] [Understanding SwiftUI's ViewModifiers and ViewBuilders](https://vinsol.com/blog/2020/07/02/understanding-swiftuis-viewmodifiers-and-viewbuilders/)

    &#x20;\- VINSOL
* [x] [The power of @ViewBuilder in SwiftUI](https://swiftwithmajid.com/2019/12/18/the-power-of-viewbuilder-in-swiftui/) - Swift with Majid
* [x] [View Builders](https://netsplit.com/swiftui/view-builders/) - NetSplit.com
* [x] [新版 SwiftUI 的 ViewBuilder 可以輸入 if let，switch & 宣告變數常數](https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E5%95%8F%E9%A1%8C%E8%A7%A3%E7%AD%94%E9%9B%86/swiftui-%E7%9A%84-viewbuilder-%E5%8F%AF%E4%BB%A5%E8%BC%B8%E5%85%A5-if-let-switch-%E4%BA%86-48601cac88ba) - 彼得潘
{% endtab %}

{% tab title="👥 相關" %}
* [result-builders](../../swift/attributes/result-builders "mention")
* [some](../../swift/type/category/some-any-generics/some "mention")
* can we change a [state](state/value/state "mention") var in ViewBuilder closure❓
* [Closures](../../swift/type/category/basic/closure)
* [adaptive-layout](layout/adaptive-layout "mention")
{% endtab %}

{% tab title="🗣 討論 " %}
* [what is “a block containing no statements”?](https://stackoverflow.com/questions/64202039/what-is-a-block-containing-no-statements)&#x20;
* [Change @State in a @ViewBuilder closure](https://stackoverflow.com/questions/57374162/change-state-in-a-viewbuilder-closure) ⭐️
* [Alternative to switch statement in SwiftUI ViewBuilder block?](https://stackoverflow.com/questions/56736466/alternative-to-switch-statement-in-swiftui-viewbuilder-block)
* Reddit ⟩ [Switch statements inside function builders are part of Swift 5.3](https://www.reddit.com/r/SwiftUI/comments/hebg5d/comment/fvqlv2n/?utm_source=share\&utm_medium=web2x\&context=3)
{% endtab %}
{% endtabs %}

## ViewBuilder as a Parameter

{% tabs %}
{% tab title="⭐️ 重點" %}
{% hint style="info" %}
* You typically use **ViewBuilder** as a **parameter** attribute for child view-producing **closure** parameters, allowing those closures to provide multiple child views.\

* 通常 @**ViewBuilder** closure 具有 @**escaping** 的特性，因為一般這個 closure 會在 .**init**(content:) 中傳進來，然後在 var **body** 中用到，如果沒有事先設為 @**escaping** 的話，那麼這個 closure 在 .**init** 中傳進來後，會**隨著 .init 結束而消失**，因此也沒辦法事後在 var **body** 中用到，所以會產生 compiler 錯誤。\
  \
  問：那麼 @**escaping** closure 會存放在那裏呢 🤔❓\
  答：通常會存放在**函數外部的變數或陣列**中。\

* 但反過來說，如果傳進來的 @**ViewBuilder** closure 馬上在 .**init** 中就用掉了，這時就不需要加 @**escaping** 這個屬性了。
{% endhint %}
{% endtab %}

{% tab title="📘 手冊" %}
* SwiftUI  ⟩  [ViewBuilder](https://developer.apple.com/documentation/swiftui/viewbuilder)
* [Escaping Closures](https://docs.swift.org/swift-book/LanguageGuide/Closures.html#ID546)
{% endtab %}

{% tab title="🗣 討論" %}
* [Why is @ViewBuilder closure escaping?](https://stackoverflow.com/questions/64089363/why-is-viewbuilder-closure-escaping)
* [Optional @ViewBuilder closure](https://stackoverflow.com/questions/60687912/optional-viewbuilder-closure)
{% endtab %}
{% endtabs %}

## Using ViewBuilder ⭐️ <a href="#steps" id="steps"></a>

共有四個步驟：

{% tabs %}
{% tab title="1" %}
```swift
// ⭐️ Step 1: Create a View struct like this:
// ⭐️ Note  : `Content` conforms to `View`
struct MyContainerView<Content: View>: View {
    ...
}
```
{% endtab %}

{% tab title="2" %}
```swift
// ⭐️ Step 2: Add `content` property and initializer
struct MyContainerView<Content: View>: View {
    let content: Content
    init(@ViewBuilder content: () -> Content) { 
        self.content = content() 
    } 
}
```
{% endtab %}

{% tab title="3" %}
```swift
// ⭐️ Step 3: Implementing `View` protocol requirement
struct MyContainerView<Content: View>: View {

    let content: Content
     
    var body: some View {
        // use `content` somewhere in your code
        content
        // ... (other customizations)
    }
}
```
{% endtab %}

{% tab title="4" %}
```swift
// ⭐️ Step 4: Use `MyContainerView` just like a `VStack` or `HStack`
struct ContentView: View {
    var body: some View {
        MyContainerView {
            // content here ...
        }
    }
}
```
{% endtab %}

{% tab title="template" %}
```swift
// ⚠️ 模板使用注意事項：
//   1. 記得更改 `MyContainerView` 名稱。
//   2. 只要填寫步驟 ⭐️ 4，其他步驟不需更動。
struct MyContainerView<Content: View>: View {

    // ⭐️ 1. `content` 屬性
    //       `MyContainerView` 的原始內容，由 .init(content:) 傳進來。
    let content: Content
    
    // ⭐️ 2. `init()`
    //       `content` 是一個擁有 @ViewBuilder 屬性的 closure，
    //        所以可以像用 `HStack` 一樣使用 `MyContainerView`。
    init(@ViewBuilder content: () -> Content) { 
        self.content = content() 
    }
    
    // ⭐️ 3. 遵循 `View` 協定
    var body: some View {
        // ⭐️ 4. 調整 `content` 成為你要的樣子。
    }
}
```
{% endtab %}
{% endtabs %}

## Examples <a href="#example" id="example"></a>

{% tabs %}
{% tab title="📦  Card" %}
```swift
// ⭐️ generic structure
struct Card<Content> : View where Content : View {
    
    // ⭐️ 卡片的內容，由 .init(content:) 傳進來。
    // ⭐️ `Content` 的型別也是由 init(content:) 決定。
    var content: Content
    
    // init(content:)
    init(@ViewBuilder content: () -> Content) {  // ⭐️ @ViewBuilder 
        self.content = content()
    }
    
    // view body
    var body: some View {
        // 卡片內容
        content
            // 卡片風格
            .padding()                  // 留白邊
            .foregroundColor(.black)    
            .background(Color.white)    // 白背景
            .cornerRadius(8)            // 截圓角
            .shadow(radius: 4)          // 畫陰影
    }
}
```
{% endtab %}

{% tab title="解說" %}
在 📦  Card 中：

```swift
init(@ViewBuilder content: () -> Content)
```

`content` 是一個擁有 **@ViewBuilder** 屬性的 **closure**， 因此我們在使用 `Card` 的時候，可以像用 `HStack` 一樣， 使用下面的語法：&#x20;

```swift
Card { ... }
```

其中 `{ ... }` 就是 `content` closure。&#x20;

但這個 closure 並不是一般的 closure，它是一個 **ViewBuilder**， 意思就是說：這個 closure 裡面「**每一行程式碼**」所產生的 view都會變成 ViewBuilder 裡面的某個 **buildBlock**() 的「參數」，例如我們輸入：&#x20;

```swift
Card {
    C0
    C1
}
```

這段程式碼其實會呼叫 ViewBuilder 的： .**buildBlock**(C0, C1) 函數，然後產生一個新的 view 出來，而這個 view 的**型別**就會被自動設定成 `Content`。以上面的例子來說，

```swift
Content == Card<TupleView<C0, C1>>
```
{% endtab %}

{% tab title="💈 範例" %}
![💈 範例](https://1830103165-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M5-JmwCZMKh_d7RfBaN%2F-MHAdZtJUmihnrA1rvEv%2F-MHAe4m2Dmmjo4IiO6Fi%2Fcard.png?alt=media\&token=11e7da64-8220-4345-af17-d58476d1fe0a)

```swift
import SwiftUI
import PlaygroundSupport

// live view
struct ContentView: View {
    var body: some View {
        Card {                // 📦 Card
            HStack {
                Image(systemName: "person.circle")
                Text("Hello World")
            }.font(.largeTitle)
        }.padding(40).background(Color.gray)
    }
}

PlaygroundPage.current.setLiveView(ContentView())
```
{% endtab %}

{% tab title="📗 參考" %}
* [View Builders](https://netsplit.com/swiftui/view-builders/) - NetSplit.com
{% endtab %}
{% endtabs %}
