簡化大量 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 {
NavigationView {
List {
ForEach(songs, id: \.self) { (song) in
Text(song)
.font(.system(size: 24, weight: .bold))
.foregroundColor(.white)
.shadow(radius: 20)

}
}
.navigationBarTitle("戴佩妮")

}
}
}
struct LyricsView: View {
var body: some View {
ZStack {
Image("rain")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.vertical)
VStack {
Spacer()
Text("突然一場大雨\n淋濕了全世界\n在模糊之中唯一清楚的只有你的臉\n突然一陣好奇\n將我推到你面前\n試着透過你的眼\n還原一世的從前")
.font(.system(size: 24, weight: .bold))
.foregroundColor(.white)
.shadow(radius: 20)

}
}
}
}

每次都要輸入 3 個 modifier 實在有點辛苦,有沒有偷懶的方法呢?

有的,以下兩個方法都可簡化大量的 modifier 程式。

方法1: 自訂組合 modifier 的 function

在 extension Text 定義 function songStyle。

extension Text {
func songStyle() -> some View {
self
.font(.system(size: 24, weight: .bold))
.foregroundColor(.white)
.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 SwiftUIstruct 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(.white)
.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
VStack {   Spacer()   Text("突然一場大雨\n淋濕了全世界\n在模糊之中唯一清楚的只有你的臉\n突然一陣好奇\n將我推到你面前\n試着透過你的眼\n還原一世的從前")      .modifier(SongTextViewModifier())}

搭配參數生成 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(.white)
.shadow(radius: 20)
}
}

生成 SongTextViewModifier 時指定想要 size 24 的文字。

Text("突然一場大雨\n淋濕了全世界\n在模糊之中唯一清楚的只有你的臉\n突然一陣好奇\n將我推到你面前\n試着透過你的眼\n還原一世的從前")
.modifier(SongTextViewModifier(size: 24))

透過 extension 讓 custom modifier 更容易呼叫

.modifier(SongTextViewModifier(size: 24)) 設定 custom modifier 沒什麼問題,但在 SwiftUI 的程式裡跟其它的 modifier 放在一起,卻顯得格格不入的感覺,比方以下例子:

Text("突然一場大雨\n淋濕了全世界\n在模糊之中唯一清楚的只有你的臉\n突然一陣好奇\n將我推到你面前\n試着透過你的眼\n還原一世的從前")
.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("突然一場大雨\n淋濕了全世界\n在模糊之中唯一清楚的只有你的臉\n突然一陣好奇\n將我推到你面前\n試着透過你的眼\n還原一世的從前")
.songTextStyle(size: 24)
.padding()
.background(Color.yellow)
.blur(radius: 1)

其它參考資訊

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
彼得潘的 iOS App Neverland

彼得潘的 iOS App Neverland

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