Разработка с использованием SwiftUI Мне трудно повторно использовать код, составляющий представления вместе.Я покажу вам простой пример: допустим, у нас есть текстовое поле в нашем приложении с определенным пользовательским интерфейсом.Давайте назовем это текстовое поле 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()
}
}
Теперь, скажем, мы хотим иметь другое текстовое полес тем же интерфейсом, но для использования в безопасных контекстах.Это текстовое поле называется 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) для выполнения этой общей задачи.Можете ли вы указать мне правильный «шаблон проектирования» или, возможно, улучшить одно из описанных мной решений?Извините за длинный вопрос.