🌅 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? 了。
因為 Value == Int、Content == 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())/*
* #todo : turn it into a modifier ?
*/
import SwiftUI
/// # 📦 Unwrap
/// unwraps a value (of type `Value`) and turns it
/// into `some View` (== `Optional<Content>`).
struct Unwrap<Value, Content: View>: View {
private let value: Value? // value to be unwrapped
private let turnValueIntoContent: (Value) -> Content
// Unwrap(_:then:)
// Unwrap(_:) { /* ... */ }
init(
_ value: Value?,
@ViewBuilder then turnValueIntoContent: @escaping (Value) -> Content
) {
self.value = value
self.turnValueIntoContent = turnValueIntoContent
}
var body: some View {
value.map(turnValueIntoContent) // Optional<Content>
}
}// ⭐️ required: 📦 Unwrap
import SwiftUI
import PlaygroundSupport
// 📦 MyView
struct MyView: 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 {
// stack of `Unwrap`s
VStack {
Unwrap(value1) { // 📦 Unwrap
Color.red.overlay(Text("\($0)"))
}
Unwrap(value2) {
Color.orange.overlay(Text("\($0)"))
}
}.border(Color.blue, width: 3)
// toggles
HStack {
Toggle(isOn: $isValue1Nil) {
Text("value1 is nil")
}
Toggle(isOn: $isValue2Nil) {
Text("value2 is nil")
}
Spacer()
}
.padding()
.overlay(Rectangle().stroke(Color.gray, style: .init(dash: [6])))
} // VStack (container)
.padding()
.border(Color.gray, width: 3)
}
}
struct ContentView: View {
var body: some View {
MyView() // ⭐️ required: 📦 MyView
}
}
PlaygroundPage.current.setLiveView(ContentView())Last updated
Was this helpful?