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
}
}
}

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com