iT邦幫忙

2023 iThome 鐵人賽

DAY 4
0
Mobile Development

SwiftUI 的大大小小系列 第 4

Day 4 - 在 SwiftUI 的 View 達成以 protocol 為 @ObservedObject 屬性的型別

  • 分享至 

  • xImage
  •  

hero

前一篇是提到「SwiftUI 中的形狀元件與運用」,雖然本系列文章基本上沒有前後關聯,如果你是還沒讀過前一篇的讀者,也推薦你去讀讀。

問題描述

在 MVVM 架構中,一個 SwiftUI view 會搭配一個 view model 。而這個 view model property 會加上 @ObservedObject 這個 property wrapper 。

struct NameCard: View {
    @ObservedObject var viewModel: NameCardViewModel
    var body: some View {
        Text(viewModel.name)
    }
}

class NameCardViewModel: ObservableObject {
    @Published var name: String = "海德格救救救救我"
}

例如這個 view ,我們可以看到 NameCard 是直接使用 NameCardViewModel 這個具體型別 (concrete type/class) ,而不是 protocol 。

身為寫 Swift 的工程師,我們都愛 POP ,不是手繪的那個,而是蘋果在 WWDC15 開始提及的 "Protocol-Oriented Programming" 。因此我們通常會希望 view 會相依在抽象型別 (abstract type) 或在 Swift 我們叫它 protocol 。

套用 protocol 會發生的問題

struct NameCard: View {
    @ObservedObject var viewModel: NameCardViewModelProtocol
    var body: some View {
        Text(viewModel.name)
    }
}

protocol NameCardViewModelProtocol: ObservableObject {
    var name: String { get }
}

class NameCardViewModel: NameCardViewModelProtocol {
    @Published var name: String = "海德格救救救救我"
}

稍微改寫一下成這樣,但是在第二行 viewModel 宣告的地方會有錯誤

0401

Type 'any NameCardViewModelProtocol' cannot conform to 'ObservableObject'

Use of protocol 'NameCardViewModelProtocol' as a type must be written 'any NameCardViewModelProtocol'

Replace 'NameCardViewModelProtocol' with 'any NameCardViewModelProtocol'

等等一些看了也沒有實質意義的錯誤訊息

簡而言之就是 @ObservedObject 和 protocol 不和,這時候的出口就是只能靠泛型 (generic) 進場解決。

泛型

struct NameCard<ViewModel: NameCardViewModelProtocol>: View {
//             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. 加上泛型
    @ObservedObject var viewModel: ViewModel
    //                             ^^^^^^^^^ 2. 替換成泛型的型別
    var body: some View {
        Text(viewModel.name)
    }
}

步驟

  1. 加上泛型的定義,並特定型別到 view model 的 protocol
  2. 替換掉 viewModel 的型別為泛型的型別

基本上只要這兩個步驟即可

如果有自定義的 init 方法,也只要把 parameter 的型別換成泛行所定義的名稱即可

init(viewModel: ViewModel) {
//              ^^^^^^^^^
    self.viewModel = viewModel
}

iOS 17+ 或其他平台的新版本

iOS 17 等在今年的更新的多了一個 Observable 可以簡化實作上的繁雜。但由於還沒進正式版,所以我認為目前的解法仍然是有效的。至於 OS 更新之後該怎麼辦,我們可以日後再擇篇詳述。

結語

今天的 SwiftUI 大大小小雖然沒有和畫面有直接關係,但是提到了該怎麼解決當想要套用 MVVM 和 POP 時會碰到的問題。

如果有更好的解法,或是 SDK 更新需要做相對應得變更,歡迎留言回饋!

那今天的 SwiftUI 的大大小小就到這邊,
以上,明天見!

環境

  • Xcode 15 beta 8

本篇使用到的 UI 元件和 modifiers 基本上沒有受到版本更新影響,因此在 Xcode 14 等環境下使用也是沒有問題的。


上一篇
Day 3 - SwiftUI 中的形狀元件與應用
下一篇
Day 5 - SwiftUI 和 Optional 屬性的綁定
系列文
SwiftUI 的大大小小30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言