使用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: 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: 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, 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 , 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 secure { SecureField("", text: $text) } else { TextField("", text: $text) } Divider() } .padding() } }
用法:
MyTextField(text: $text, label: "Label") // unsecure MyTextField(text: $text, label: "Label", secure: true) // secure