使用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