🌅 Unwrap

可以解開一個 Optional 的值,並將此值轉換成一個 View (Optional<Content>)。

只要使用 .map 的技巧,可以讓 Unwrap 變成完全沒有必要的存在。

定義

struct Unwrap<Value, Content: View>: View

📦 Unwrap 可以解開一個型別為 Value? 的值,然後利用內部的一個 @ViewBuilder closure turnValueIntoContent: (Value) -> Content 將此值轉換成型別為 Content? == Optional<Content> 的一個 View。

語法

使用 Unwrap 時,要提供它

  • 一個 Value? 的值

  • 一個可以將 Value 轉換為 Content@ViewBuilder closure

然後 Unwrap 就可以利用這個 closure 與 .map() 將 Value? 轉換為 Content? 。最後再透過 body 這個屬性將這個 Content? 呈現出去。

Unwrap(value, then: { /* turn value into content view */ })
Unwrap(value) { /* turn value into content view */ }

在下面的例子中,我們將一個 Int? (Value == Int) 送給 Unwrap 處理,然後在 Unwrap 內部透過 .map(turnValueIntoContent) 將 Int? 轉換為 Text?

{ Text(\"$0") } 這個 @ViewBuilder closure 告訴我們:turnValueIntoContent 會把 Int 轉換為 Text,所以這個例子中的 Content == Text。這同時也告訴我們為什麼 .map(turnValueIntoContent) 會將 Int? 轉換為 Text? 了。

f: A -> B 函數用 .map() 包起來,這時 .map(f) 就會把 A? 轉換成 B?

因為 Value == IntContent == Text,所以這個例子的 Unwrap 的完整型別為:Unwrap<Int, Text>

let value: Int? = 1

let view = Unwrap(value) {
   Text("\($0)")            // Content == Text
}

let T = type(of: view)      // Unwrap<Int, Text>.Type
print(T)                    // Unwrap<Int, Text>
print(T.Body.self)          // Optional<Text>

// ⭐️ use `T.self` to reference the type object itself
T.Body.self == Text?.self   // true

程式碼

import SwiftUI
import PlaygroundSupport

// live view
struct ContentView: View {
    
    @State private var isValue1Nil = false
    @State private var isValue2Nil = false
    
    var value1: Int? { isValue1Nil ? nil : 1}
    var value2: Int? { isValue2Nil ? nil : 2}
    
    var body: some View {
        VStack {
            
            VStack {
                // ⭐️ use .map
                value1.map {
                    Color.red.overlay(Text("\($0)"))
                }.animation(.default)
                // ⭐️ use .map
                value2.map {
                    Color.orange.overlay(Text("\($0)"))
                }.animation(.default)
            }.border(Color.blue, width: 3)
                
            // toggles
            HStack(spacing: 60) {
                Toggle(isOn: $isValue1Nil) {
                    Text("value1 is nil")
                }
                Toggle(isOn: $isValue2Nil) {
                    Text("value2 is nil")
                }
            } // toggles
                .padding()
                .overlay(Rectangle().stroke(Color.gray, style: .init(dash: [6])))
            
        } // VStack (container)
            .padding().border(Color.gray, width: 3)
    }
}

PlaygroundPage.current.setLiveView(ContentView())

Last updated