SwiftUI: повторное использование кода с составом представления - PullRequest
1 голос
/ 25 сентября 2019

Разработка с использованием SwiftUI Мне трудно повторно использовать код, составляющий представления вместе.Я покажу вам простой пример: допустим, у нас есть текстовое поле в нашем приложении с определенным пользовательским интерфейсом.Давайте назовем это текстовое поле MyTextField.Пользовательский интерфейс может быть:

enter image description here

Вот код:

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) для выполнения этой общей задачи.Можете ли вы указать мне правильный «шаблон проектирования» или, возможно, улучшить одно из описанных мной решений?Извините за длинный вопрос.

Ответы [ 2 ]

5 голосов
/ 25 сентября 2019

Вы можете использовать простой 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
4 голосов
/ 25 сентября 2019

Ваша первая попытка - правильный подход, но вместо того, чтобы позволить вызывающей стороне предоставить текстовое поле, добавьте статические методы для различных типов полей:

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)
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...