簡化大量 modifier 程式的兩個方法,自訂組合 modifier 的 function 和透過 ViewModifier 實作 custom modifier
開發 SwiftUI App 時,我們時常透過大量的 modifier 客製元件,將畫面變成賞心悅目的樣子。不過 App 裡很多元件都是類似的設計,比方以下 SongList & LyricsView 裡的文字都是橘色粗體加陰影,透過以下 3 個 modifier 實現。
- .font(.system(size: 24, weight: .bold))
- .foregroundColor(.white)
- .shadow(radius: 20)
struct SongList: View {
let songs = ["街角的祝福", "光著我的腳丫子", "怎樣", "愛瘋了", "誤解"]
var body: some View {
NavigationStack {
List(songs, id: \.self) { song in
Text(song)
.font(.system(size: 24, weight: .bold))
.foregroundColor(.orange)
.shadow(radius: 20)
}
.navigationTitle("戴佩妮")
}
}
}
struct LyricsView: View {
let lyrics: String
var body: some View {
ZStack {
Rectangle()
.fill(.black.gradient)
.ignoresSafeArea()
ScrollView {
Text(lyrics)
.font(.system(size: 24, weight: .bold))
.foregroundColor(.orange)
.shadow(radius: 20)
}
.padding()
}
}
}
每次都要輸入 3 個 modifier 實在有點辛苦,有沒有偷懶的方法呢?
有的,以下兩個方法都可簡化大量的 modifier 程式。
方法1: 自訂組合 modifier 的 function
在 extension Text 定義 function songStyle。
extension Text {
func songStyle() -> some View {
self
.font(.system(size: 24, weight: .bold))
.foregroundColor(.orange)
.shadow(radius: 20)
}
}
之後從 Text 呼叫 function songStyle 即可讓文字有橘色粗體陰影。
ForEach(songs, id: \.self) { (song) in
Text(song)
.songStyle()
}
方法 2: 透過 ViewModifier 實作 custom modifier
定義 custom modifier
- 定義遵從 protocol ViewModifier 的 SongTextViewModifier。
import SwiftUI
struct SongTextViewModifier: ViewModifier {
}
- 定義 protocol ViewModifier 的 function body。
輸入 body 後從自動完成選單選擇 body。
- 在 function body 裡呼叫想設定的 modifier。
body 的參數 content 將是我們想修改的 view,因此我們從 content 呼叫想設定的 modifier。
struct SongTextViewModifier: ViewModifier {
func body(content: Content) -> some View {
content
.font(.system(size: 24, weight: .bold))
.foregroundColor(.orange)
.shadow(radius: 20)
}
}
設定 custom modifier
呼叫 function modifier,傳入自訂的 modifier,即可一行套用多個 modifier。比方傳入 SongTextViewModifier()
,套用橘色粗體加陰影的文青文字樣式。
- SongList
Text(song) 將成為 SongTextViewModifier 的 function body 收到的參數 content。
ForEach(songs, id: \.self) { song in
Text(song)
.modifier(SongTextViewModifier())
}
- LyricsView
Text(song) 將成為 SongTextViewModifier 的 function body 收到的參數 content。
ScrollView {
Text(lyrics)
.modifier(SongTextViewModifier())
}
.padding()
搭配參數生成 custom modifier
剛剛的 SongTextViewModifier 將產生大小 24 的文字,若我們需要不同大小的文字,難道要為了它另外定義一個 custom modifer ?
不用這麼麻煩,我們可以在 custom modifier 加上 property,比方代表文字大小的 size,然後在產生 SongTextViewModifier 時傳入文字的大小。
struct SongTextViewModifier: ViewModifier {
let size: CGFloat
func body(content: Content) -> some View {
content
.font(.system(size: size, weight: .bold))
.foregroundColor(.orange)
.shadow(radius: 20)
}
}
生成 SongTextViewModifier 時指定想要 size 24 的文字。
Text(lyrics)
.modifier(SongTextViewModifier(size: 24))
透過 extension 讓 custom modifier 更容易呼叫
以 .modifier(SongTextViewModifier(size: 24))
設定 custom modifier 沒什麼問題,但在 SwiftUI 的程式裡跟其它的 modifier 放在一起,卻顯得格格不入的感覺,比方以下例子:
Text(lyrics)
.modifier(SongTextViewModifier(size: 24))
.padding()
.background(Color.yellow)
.blur(radius: 1)
能不能讓它看起來就像一般的 modifier function,就像 padding,blur 一樣,直接以功能的名字呼叫,而不用先呼叫 function modifier 呢 ?
可以的,我們可以 extension View,在裡面定義 function songTextStyle 代表自訂的 modifier function。
- 寫法1
呼叫 function modifier。
extension View {
func songTextStyle(size: CGFloat) -> some View {
modifier(SongTextViewModifier(size: size))
}
}
- 寫法2
function modifier 的宣告如下
func modifier<T>(_ modifier: T) -> ModifiedContent<Self, T>
它的回傳型別是 ModifiedContent,因此我們也可以直接生成 ModifiedContent,在參數 content 傳入 self,modifier 傳入自訂的 SongTextViewModifier。
extension View {
func songTextStyle(size: CGFloat) -> some View {
ModifiedContent(content: self, modifier: SongTextViewModifier(size: size))
}
}
透過剛剛定義的 extension,之後 SwiftUI 任何的 view 都可以呼叫 songTextStyle,把它當成一般的 modifier 來使用。
Text(lyrics)
.songTextStyle(size: 24)
.padding()
.background(Color.yellow)
.blur(radius: 1)
將自訂的 SwiftUI modifier 加到 Modifiers library
我們也可以將自訂的 modifier 加到 Modifiers library,定義以下程式讓 Song Text Style 出現在 Modifiers library。
struct Previews_AppleView_LibraryContent: LibraryContentProvider {
func modifiers(base: any View) -> [LibraryItem] {
LibraryItem(base.songTextStyle(size: 20))
}
}