Reputation: 894
I am creating a custom TextField
view that consists of multiple adornment views. I want to be able to set up the inner TextField
with view modifiers such as keyboard, capitalization, etc. that apply just to that sub-view.
Rather than creating properties for each of these I figured the best way would be to pass in a single optional ViewModifier
parameter and use it something like this:
struct MySuperTextField: View {
var vm: ViewModifier?
var body: some View {
TextField(...)
.modifier( vm ?? EmptyModifier() )
// ... more views here
}
}
This doesn't work due to the associatedType
in ViewModifier
. Alas there is no such thing as AnyViewModifier either (and I could't figure out how to make one that worked).
Anyone manage to do something like this? I couldn't find anything searching the web.
An example would be
struct LastNameModifier: ViewModifier {
func body(content: Content) -> some View {
content
.autocapitalization(.words)
.textContentType(.familyName)
.backgroundColor(.green)
// ... anything else specific to names
}
}
struct EmailModifier: ViewModifier {
func body(content: Content) -> some View {
content
.keyboardType(.emailAddress)
.textContentType(.emailAddress)
.backgroundColor(.yellow)
// ... anything else specific to emails
}
}
and then use them with my MySuperTextField
like this:
VStack {
MySuperTextField("Last Name", $lastName, vm: LastNameModifier())
MySuperTextField("Email", $email, vm: EmailModifier())
}
Upvotes: 8
Views: 8091
Reputation: 54621
If I understood correctly, you can make your MySuperTextField
accept a generic parameter:
struct MySuperTextField<V>: View where V: ViewModifier {
private let placeholder: String
@Binding private var text: String
private let vm: V
init(_ placeholder: String, text: Binding<String>, vm: V) {
self.placeholder = placeholder
self._text = text
self.vm = vm
}
var body: some View {
TextField(placeholder, text: $text)
.modifier(vm)
}
}
Then, you can pass some ViewModifier as the parameter:
struct ContentView: View {
@State private var text: String = "Test"
var body: some View {
MySuperTextField("Last Name", text: $text, vm: LastNameModifier())
}
}
If you need a way to skip the vm
parameter when creating MySuperTextField
:
MySuperTextField("Last Name", text: $text)
you can create an extension:
extension MySuperTextField where V == EmptyModifier {
init(_ placeholder: String, text: Binding<String>) {
self.placeholder = placeholder
self._text = text
self.vm = EmptyModifier()
}
}
Upvotes: 8
Reputation: 1172
since ViewModifier
has an associated type that you cannot use as an instance variable but I propose you another way of use:
MySuperTextField
does not have to maintain instances of the ViewModifier
type.
struct MySuperTextField: View {
@State var textValue = ""
var body: some View {
TextField("", text: $textValue)
}
}
For each structure that conforms to the ViewModifier
protocol you create an extension:
extension MySuperTextField {
func lastNameModifier() -> some View {
modifier(LastNameModifier())
}
func emailModifier() -> some View {
modifier(EmailModifier())
}
}
and you can use it this way:
struct ContentView : View {
var body: some View {
VStack {
MySuperTextField(textValue: "Last Name")
.lastNameModifier()
MySuperTextField(textValue: "Email")
.emailModifier()
}
}
}
Upvotes: 1
Reputation: 9755
Set the ViewModifier up as a struct and just call it on the view. You just have to be specific as to the type, such as this buttonStyle modifier I used:
struct PurchaseButtonStyle: ButtonStyle {
let geometry: GeometryProxy
func makeBody(configuration: Configuration) -> some View {
configuration.label
.frame(minWidth: 0, idealWidth: 300, maxWidth: .infinity)
.padding(.all, geometry.size.height / 30)
.foregroundColor(.black)
.background(.orange)
.cornerRadius(30)
}
}
The use is:
Button(...)
.buttonStyle(PurchaseButtonStyle(geometry: Geometry))
You just have to write it to be the specific modifier. I just used this because it was handy in an app I was working on.
Upvotes: 0