[英]SwiftUI: code reuse with view composition
使用 SwiftUI 進行開發我發現很難一起重用代碼組合視圖。 我將向您展示一個簡單的示例:假設我們的應用中有一個帶有特定 UI 的文本字段。 讓我們將此文本字段稱為MyTextField
。 用戶界面可能是:
這是代碼:
struct MyTextField: View {
@Binding var text: String
var label: String
var body: some View {
VStack {
HStack {
Text(label)
Spacer()
}
TextField("", text: $text) //here we have a simple TextField
Divider()
}
.padding()
}
}
現在,假設我們想要另一個具有相同 UI 的文本字段,但要在安全上下文中使用。 此文本字段稱為MySecureTextField
。 在這種情況下,我應該使用SecureField
而不是TextField
,但顯然我不想以這種方式創建一個全新的視圖:
struct MySecureTextField: View {
@Binding var text: String
var label: String
var body: some View {
VStack {
HStack {
Text(label)
Spacer()
}
SecureField("", text: $text) //this time we have a SecureField here
Divider()
}
.padding()
}
}
我該如何設計這樣的情況? 我嘗試了幾種方法,但沒有一種方法看起來是正確的:
1 - 第一次嘗試擁有一種將實際文本字段作為參數的容器視圖:
struct TextFieldContainer<ActualTextField>: View where ActualTextField: View {
private let actualTextField: () -> ActualTextField
var label: String
init(label: String, @ViewBuilder actualTextField: @escaping () -> ActualTextField) {
self.label = label
self.actualTextField = actualTextField
}
var body: some View {
VStack {
HStack {
Text(label)
Spacer()
}
actualTextField()
Divider()
}
.padding()
}
}
我可以這樣使用TextFieldContainer
:
struct ContentView: View {
@State private var text = ""
var body: some View {
TextFieldContainer(label: "Label") {
SecureField("", text: self.$text)
}
}
}
我不喜歡這個解決方案:我不想指定實際的文本字段,它應該隱含在視圖本身( MyTextField
或MySecureTextField
)中。 這樣我什至可以在容器中注入任何類型的視圖,而不僅僅是一個文本字段。
2 - 第二次嘗試擁有一個私有容器和兩個在內部使用該容器的公共視圖:
private struct TextFieldContainer<ActualTextField>: View where ActualTextField: View {
//...
//the same implementation as above
//...
}
struct MyTextField: View {
@Binding var text: String //duplicated code (see MySecureTextField)
let label: String //duplicated code (see MySecureTextField)
var body: some View {
TextFieldContainer(label: label) {
TextField("", text: self.$text)
}
}
}
struct MySecureTextField: View {
@Binding var text: String //duplicated code (see MyTextField)
let label: String //duplicated code (see MyTextField)
var body: some View {
TextFieldContainer(label: label) {
SecureField("", text: self.$text)
}
}
}
並以這種方式使用它們:
struct ContentView: View {
@State private var text = ""
@State private var text2 = ""
var body: some View {
VStack {
MyTextField(text: $text, label: "Label")
MySecureTextField(text: $text2, label: "Secure textfield")
}
}
}
我並不是真的不喜歡這個解決方案,但是屬性上有一些代碼重復。 如果有很多屬性,就會有很多代碼重復。 此外,如果我更改了TextFieldContainer
上的某些屬性,我應該因此更改所有視圖,可能需要更改很多結構( MyTextField
、 MySecureTextField
、 MyEmailTextField
、 MyBlaBlaTextField
等)。
3 - 我的最后一次嘗試使用與上面第二次嘗試相同的方法,但以這種方式使用AnyView
:
struct MySecureTextField: View {
private let content: AnyView
init(text: Binding<String>, label: String) {
content = AnyView(TextFieldContainer(label: label) {
SecureField("", text: text)
})
}
var body: some View {
content
}
}
struct MyTextField: View {
private let content: AnyView
init(text: Binding<String>, label: String) {
content = AnyView(TextFieldContainer(label: label) {
TextField("", text: text)
})
}
var body: some View {
content
}
}
這與第二次嘗試沒有什么不同,我的直覺是我錯過了完成這項常見任務的正確方法(SwiftUI-y 方式)。 您能否指出正確的“設計模式”或改進我描述的解決方案之一? 對不起,很長的問題。
你可以使用一個簡單的 if!
struct MyTextField: View {
@Binding var text: String
var label: String
var secure: Bool = false
var body: some View {
VStack {
HStack {
Text(label)
Spacer()
}
if (self.secure) {
SecureField("", text: $text)
} else {
TextField("", text: $text)
}
Divider()
}
.padding()
}
}
用法:
MyTextField(text: $text, label: "Label") // unsecure
MyTextField(text: $text, label: "Label", secure: true) // secure
您的第一次嘗試是正確的方法,但不是讓調用者提供文本字段,而是為不同的字段類型添加 static 方法:
struct TextFieldContainer<FieldView>: View where FieldView: View {
var label: String
var body: some View {
VStack {
HStack {
Text(label)
Spacer()
}
fieldView
Divider()
}
.padding()
}
fileprivate init(label: String, fieldView: FieldView) {
self.label = label
self.fieldView = fieldView
}
private let fieldView: FieldView
}
extension TextFieldContainer where FieldView == TextField<Text> {
static func plain(label: String, text: Binding<String>) -> some View {
return Self(label: label, fieldView: TextField("", text: text))
}
}
extension TextFieldContainer where FieldView == SecureField<Text> {
static func secure(label: String, text: Binding<String>) -> some View {
return Self(label: label, fieldView: SecureField("", text: text))
}
}
示例使用:
struct ContentView: View {
@State private var text = ""
var body: some View {
VStack {
TextFieldContainer.plain(label: "Label", text: $text)
TextFieldContainer.secure(label: "Label", text: $text)
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.