Optional 型別的 map function 和 SwiftUI 的結合
Swift 有許多 optional 的相關語法,為了安全我們通常採用 if let 或 guard let 讀取 optional 的內容後再做運算,不過 Optional 型別的 map function 也是滿不錯的方法,接下來就讓我們一步步打開它神秘的面紗吧。
Optional 型別的 map function
比方以下例子,我們利用 if let 讀取 name 後再將它印出。
let name: String? = "peter"if let name = name { print(name)}
透過型別 Optional 的 map function ,我們可以將剛剛的程式變得更精簡。map 的宣告如下:
func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
呼叫 map 時我們可以傳入 closure,closure 只在 optional 不為 nil 時會執行。它將有一個參數,代表 optional 的內容。因此以下程式 closure 裡的參數 $0 就是字串 peter,我們不用再加上可怕的 ! 讀取。
let name: String? = "peter"name.map { print($0) }
ps: $0 是 closure 的參數省略語法,有興趣的可參考以下連結的說明。
結果: 在 console 印出 peter
peter
相反的,當 name 的內容為 nil 時,map 將直接回傳 nil,不會執行 closure 的程式。
因此,function map 回傳的型別也會是 optional, 比方以下例子我們判斷 bmi 的數字回傳描述體重是否正常的字串。
let bmi: Double? = 20
var message: String? = bmi.map {
switch $0 {
case 18.5 ..< 24:
return "完美"
case ..<18.5:
return "太瘦了"
case 24...:
return "太胖了"
default:
return ""
}
}
此時 message 的型別將是代表 optional 的 String?。
幫助 SwiftUI 處理 otpional 的 map function
開發 SwiftUI App 時,我們遇到 optional 的機率已經比以前低了不少,不過有時還是會遇到,就像夜路走多了總會遇到鬼一樣。
大大們常說遇到 optional 用 if let 讀取比較安全,不要使用暴力的 !。不過如下圖所示,當我們在 SwiftUI 常見的 VStack { } 裡使用 if let 時,卻冒出了紅色錯誤,
Closure containing control flow statement cannot be used with function builder ViewBuilder
難道是大大欺騙我們 ? 別誤會,大大真的是真心不騙,這裡錯誤的原因是因為 VStack 接收的 closure 參數 content 加了 @ViewBuilder,因此我們只能在 closure 裡寫一些 ViewBuilder 支援的語法。很遺憾的它支援了 if else,卻不支援它的好兄弟 if let。
init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
關於 ViewBuilder 的相關說明,有興趣的可參考以下連結。
看來是老天爺逼我們使用 !,我們是人在江湖,身不由己。不過我們還是可以保險一點先用 if 檢查它是否為 nil,不為 nil 時才用 ! 讀取。
struct ProfileView: View {
var userName: String
var imageName: String?
var body: some View {
VStack {
Text(userName)
if imageName != nil {
Image(imageName!)
.resizable()
.scaledToFit()
}
}
}
}struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ProfileView(userName: "Peter", imageName: "pic")
}
}
不過如果你真的很討厭 !,就像彼得潘討厭 Hook 一樣,那麼採用 Optional 型別的 map function 也是不錯的方法。我們改從 Optional 型別的 imageName 呼叫 map,當它有值時,closure 裡的程式將從參數 $0 取得圖片名字後生成 Image。
struct ProfileView: View {
var userName: String
var imageName: String?
var body: some View {
VStack {
Text(userName)
imageName.map {
Image($0)
.resizable()
.scaledToFit()
}
}
}
}
當 imageName 等於 nil 時,function map 將直接回傳 nil,而 SwiftUI 看到 nil 還是可以活得好好的,因為它很聰明,它知道 nil 的東西不需要顯示。比方以下程式變數 image 為 nil,因此 VStack 只會顯示文字。
struct ProfileView: View {
var userName: String
var image: Image?
var body: some View {
VStack {
Text(userName)
image
}
}
}