在SwiftUI:可組合的視圖中,我們已經(jīng)介紹了如何做一個良好的UI設(shè)計独悴。
在本文中例书,我們看下新組件Button
該如何設(shè)計:
開始
為了方便,我們創(chuàng)建一個新的ButtonStyle
:
struct FSButton: View {
let titleKey: LocalizedStringKey
let action: () -> Void
init(_ titleKey: LocalizedStringKey, action: @escaping () -> Void) {
self.titleKey = titleKey
self.action = action
}
var body: some View {
Button(action: action, label: { Text(titleKey).bold() })
.buttonStyle(FSButtonStyle())
}
}
private struct FSButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
Spacer()
configuration.label
.foregroundColor(.white)
Spacer()
}
.padding()
.background(
RoundedRectangle(cornerRadius: 8, style: .continuous).fill(Color.green)
)
.opacity(configuration.isPressed ? 0.5 : 1)
}
}
我們可以這么使用:
FSButton("Button title") {
// button tap action
}
這是我們的預(yù)覽效果刻炒,我們可以把它移植到項目中去了决采。
一周后
一周過去了,產(chǎn)品告訴我們坟奥,有些按鈕文本將由我們的后端提供树瞭,而不是由應(yīng)用程序處理拇厢。
為了使用我們當前的API,我們需要顯式地將后端字符串轉(zhuǎn)換為LocalizedStringKey
:
var backendString: String
...
FSButton(LocalizedStringKey(backendString), action: buttonDidTap)
然而這并不理想:
- 我們?yōu)E用
LocalizedStringKey
- 我們會觸發(fā)一個運行時查找一個我們已經(jīng)知道不會在我們的
Localizable.string
表中
處理這個問題的一種更好的方法是使用Text
的初始化方法init<S: StringProtocol>(_ content: S)
晒喷,其目的是顯示字符串的原樣孝偎,而不首先嘗試對它們進行本地化。
我們需要一種方法來同時兼容這個新的初始化方法和之前的初始化方法凉敲。一種解決方法就是用一個title
Text
替換FSButton
的titleKey
LocalizedStringKey
屬性:
struct FSButton: View {
let title: Text
let action: () -> Void
init(titleKey: LocalizedStringKey, action: @escaping () -> Void) {
self.title = Text(titleKey)
self.action = action
}
init<S: StringProtocol>(_ content: S, action: @escaping () -> Void) {
self.title = Text(content)
self.action = action
}
var body: some View {
Button(action: action, label: { title.bold() })
.buttonStyle(FSButtonStyle())
}
}
使用這種方式衣盾,FSButton
將動態(tài)地使用適當?shù)某跏蓟椒▌?chuàng)建文本實例:
FSButton(titleKey: "my_localized_title", action: { ... })
var backendString: String = ...
FSButton(backendString, action: { ... })
你現(xiàn)在知道怎么用泰語寫“Button title”了!
不久之后
營銷團隊已經(jīng)聽說了按鈕的文本后端驅(qū)動方法,現(xiàn)在他們希望我們能夠讓他們也能驅(qū)動這樣的設(shè)計:
這在我們的兩個初始化方法中是不可能實現(xiàn)的爷抓。然而势决,Text
是最靈活和動態(tài)的SwiftUI視圖之一,它有一整套專門的修飾符來返回其他Text
文本視圖,甚至可以向其他Text
文本視圖添加Text
文本視圖,結(jié)果仍然是另一個Text
文本:
let text: Text =
Text("Default ") +
Text("italic ").italic() +
Text("Big ").font(.title) +
Text("Red ").foregroundColor(.red) +
Text("underline").underline()
因此废赞,為了支持這個新需求,從FSButton
的角度來看叮姑,我們需要做的就是公開一個新的初始化方法接受一個Text
:
extension FSButton {
init(_ title: Text, action: @escaping () -> Void) {
self.title = title
self.action = action
}
}
這使得創(chuàng)建視圖成為可能唉地,例如
有了這個初始化器,我們可以完全覆蓋按鈕文本的默認樣式传透,使用不同的字體耘沼、粗細、文本顏色等朱盐,從而展示各種新的按鈕樣式群嗤。
結(jié)束
最終代碼如下:
import SwiftUI
struct ContentView: View {
var backendString: String = "some backend string"
var body: some View {
let marketingText: Text =
Text("Please please ").italic() +
Text("tap me ") +
Text("NOW!").underline().bold().font(.title)
let exampleText: Text =
Text("Default ") +
Text("italic ").italic() +
Text("Big ").font(.title) +
Text("Red ").foregroundColor(.red) +
Text("underline").underline()
VStack {
FSButton(titleKey: "my_localized_title") {}
FSButton(backendString) {}
FSButton(marketingText) {}
FSButton(exampleText) {}
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.padding()
.previewLayout(.sizeThatFits)
.environment(\.locale, .init(identifier: "th"))
}
}
自定義FSButton:
struct FSButton: View {
let title: Text
let action: () -> Void
init(_ title: Text, action: @escaping () -> Void) {
self.title = title
self.action = action
}
init<S: StringProtocol>(_ content: S, action: @escaping () -> Void) {
self.title = Text(content)
self.action = action
}
init(titleKey: LocalizedStringKey, tableName: String? = nil, bundle: Bundle? = nil, comment: StaticString? = nil, action: @escaping () -> Void) {
self.title = Text(titleKey, tableName: tableName, bundle: bundle, comment: comment)
self.action = action
}
var body: some View {
Button(action: action, label: { title.bold() })
.buttonStyle(FSButtonStyle())
}
}
private struct FSButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
Spacer()
configuration.label
.foregroundColor(.white)
Spacer()
}
.padding()
.background(
RoundedRectangle(cornerRadius: 8, style: .continuous).fill(Color.green)
)
.opacity(configuration.isPressed ? 0.5 : 1)
}
}
總結(jié)
構(gòu)建顯示文本的SwiftUI組件可能比預(yù)期的要復(fù)雜,然而兵琳,我們這邊只需很少的工作就可以覆蓋大多數(shù)用例狂秘。
我們在這里看到的同樣的方法和思考過程也被應(yīng)用到許多swiftUI視圖定義中,不同的視圖會暴露不同的初始化方法躯肌。
如果你正在尋找類似方案者春,以下是SwiftUI使用這種方法的一些思路:Button
, ColorPicker
, CommandMenu
, DatePicker
, DisclosureGroup
, Label
, Link
, Menu
, NavigationLink
, Picker
, ProgressView
, SecureField
, Stepper
, TextField
, Toggle
, WindowGroup
。還有一些modifiers修飾詞清女,如navigationTitle
钱烟。