SwiftUI:视图组合的代码重用
Posted
技术标签:
【中文标题】SwiftUI:视图组合的代码重用【英文标题】:SwiftUI: code reuse with view composition 【发布时间】:2019-09-25 20:09:39 【问题描述】:使用 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 方法)。您能否指出正确的“设计模式”或改进我描述的解决方案之一?对不起,很长的问题。
【问题讨论】:
【参考方案1】:你可以使用简单的 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
【讨论】:
感谢 Quinn,我赞成您的回答,因为这是一种简单而干净的方法,在某些简单的情况下可能值得。 只是一个建议:约定是在编写 swift 时省略括号,仅在需要时明确使用 self。如果你愿意,你可以在你的身体中使用if secure
。
@ethoooo 是的,你是对的,我在 kotlin 和 swift 之间跳来跳去,以至于我总是习惯用括号... 【参考方案2】:
您的第一次尝试是正确的方法,但不是让调用者提供文本字段,而是为不同的字段类型添加静态方法:
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)
【讨论】:
谢谢 Rob,这正是我正在寻找的通用解决方案。 嗨,Rob,很抱歉再次打扰您:您知道为什么在预览上方复制粘贴代码不再起作用吗?如果我使用您在另一个视图中创建的文本字段,则没有问题(预览有效),但文本字段本身的预览(在文本字段编码的文件中)会出现错误:replaced function 'secure(label:text:)' of type '<τ_0_0 where τ_0_0 == SecureField<Text>> (BBTextField<SecureField<Text>>.Type) -> (String, Binding<String>) -> some View' could not be found
。谢谢。以上是关于SwiftUI:视图组合的代码重用的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI 将 Swift 代码作为参数传递给可重用视图