前一篇第 5 天是提到「SwiftUI 和 Optional 屬性的綁定」,雖然本系列文章基本上沒有前後關聯,如果你是還沒讀過前一篇的讀者,也推薦你去讀讀。
就像在寫程式碼的時候會想要封裝成 method 或是另外一個 class/struct ,在 SwiftUI 當然會想做一樣的事情。
先來看本篇文章主要的例子:
Text("2023 iThome 鐵人賽")
.foregroundStyle(Color(red: 0.13, green: 0.14, blue: 0.1))
.font(.system(size: 20, weight: .black))
.background(
Rectangle()
.foregroundColor(.mint)
.frame(height: 8)
.offset(x: 6, y: 8)
)
在封裝之前,假使我們有這樣樣式的一個元件,有多種 modifiers 疊加,用了不同的顏色、尺寸等。當一個 UI 程式碼包含這麼多樣式相關的設定時,除了會 降低可讀性 ,也 無法重複利用 這個樣式。
主要分這兩個步驟,接著一一來看各自該做什麼事。
新建一個繼承 protocol VisualModifier 的 struct ,並實作指定的方法:
func body(content: Content) -> some View
實作出來的內容如下
struct TitleStyle: ViewModifier {
func body(content: Content) -> some View {
content
.foregroundStyle(Color(red: 0.13, green: 0.14, blue: 0.1))
.font(.system(size: 20, weight: .black))
.background(
Rectangle()
.foregroundColor(.mint)
.frame(height: 8)
.offset(x: 6, y: 8)
)
}
}
最簡單直接的方式,可以透過 .modifier
傳入一個 ViewModifier 物件:
Text("2023 iThome 鐵人賽")
.modifier(TitleStyle())
不過當有多個自家的 ViewModifier
s 時,就會有數個 .modifier
s 。個人認為這樣看起來變得冗余而且降低可讀性。
因此,就可以來擴展 View 、建立 helper methods ,來改善這個問題。
extension View {
func titleStyle() -> some View {
// TODO: 實作
}
}
基本型如上,就像一般的方法一樣,可以自定義不相衝的方法名稱、參數,回傳為 some View 即可。
實作內容把上面的 modifier 放入即可,如下:
extension View {
func titleStyle() -> some View {
modifier(TitleStyle())
}
}
Text("iThome 鐵人賽 2013")
.titleStyle()
像是這樣加上去即可
VisualModifier 做出來之後,不止 Text ,其他種類的 UI 元件也可以套用,例如 Label :
Label("Apple", systemImage: "apple.logo")
.titleStyle()
上半為預設樣式,只要加上一行就可以向下半部那樣套用了。
是不是很方便呀!
我手上的專案,在按鈕的部分按下去會有放大縮小 (spring animation) 的效果,為了要方便使用,就有用 ViewModifier 封裝起來。
其他的像是邏輯和 UI 元件本身有關、和商業邏輯無關的時候,也有這樣封裝。例如 SwiftUI 的 TextField 沒有 clear 按鈕(對,沒有),在實作之後,也用 ViewModifier 封裝起來。
相信會有另外一派會說,建立一個 custom view 封裝起來不就好了嗎?
沒錯,
這個部分就要看團隊的規範和取捨,看共用的樣式是不是會橫跨不同種類的 UI 元件?是不是只有用在特定情形?等等各式各樣的需要考慮的面向。
但是把這個工具記起來熟悉起來,當需要用到的時候,就能夠多一個解決方法的選項了!
到這裡就是 ViewModifier 的基本實作
如果有疑問、回饋,歡迎留言討論
那今天的 SwiftUI 的大大小小就到這邊,以上,明天見!
本篇使用到的 UI 元件和 modifiers 基本上沒有受到版本更新影響因此 Xcode 14 等環境下使用也是沒問題的。