Question reg. TextFieldStyle protocol

Hello together,

I tried to adapt a TextField with it’s own TextFieldStyle. I have found the idea and codes snippet from Peter Friese in of his YouTube-Videos. See code below (slightly modified).

struct TextInputField: View {
var title: String
@Binding var text: String

init(_ title: String, text: Binding<String>) {
    self.title = title
    self._text = text
}
var body: some View {
    ZStack(alignment: .leading) {
        Text(title)
            .padding(.leading, 10)
            .foregroundColor(text.isEmpty ? Color(.placeholderText) : .accentColor)
            .offset(y: text.isEmpty ? 0 : -20)
            .scaleEffect(text.isEmpty ? 1: 0.8, anchor: .leading)
        TextField("", text: $text)
            .textFieldStyle(MyTextFieldStyle())
    }
    .animation(.default)
}

}

struct MyTextFieldStyle: TextFieldStyle {
func _body(configuration: TextField<Self._Label>) → some View {
configuration
.padding(.top, 20)
.padding(.bottom, 8)
.padding(.leading, 8)
.background(
RoundedRectangle(cornerRadius: 4, style: .continuous)
.stroke(Color(UIColor.systemGray5), lineWidth: 1)
)
}
}

Use: TextInputField(“Email or User ID”, text: $email)

As you can see, the cursor - which is actually waiting for a text input of the TextField - is not centered. And this is a small, but for me important blemish.

Now I would like to adapt the paddings in dependence of the input text. If the text is empty, the top-padding should be a smaller number.

When the user starts to input text, it’s vertical size should increase - so it looks like

Do you have any idea, how I can realize this? Or ist it possible to pass the value of the text into MyTextFieldStyle?

Any help very much appreciated.

Thanks, Peter

@peter_luger

Hi Peter,

Not sure if this is what you want to achieve but have a look at this version of your code:

struct TextInputField: View {
    @State private var title: String
    @State private var text: String

    var body: some View {
        VStack {
            ZStack(alignment: .leading) {
                Text(title)
                    .padding(.leading, 10)
                    .foregroundColor(text.isEmpty ? Color(.placeholderText) : .accentColor)
                    .offset(y: text.isEmpty ? 0 : -15)
//                    .scaleEffect(text.isEmpty ? 1: 0.8, anchor: .leading)
                TextField("", text: $text)
                    .textFieldStyle(MyTextFieldStyle(text: $text))
                    .autocapitalization(.none)
            }
            .padding(.horizontal)
        }
    }
}

struct MyTextFieldStyle: TextFieldStyle {
    @Binding var text: String
    func _body(configuration: TextField<Self._Label>) -> some View {
        configuration
            .padding(.top, text.isEmpty ? 8 : 25)
            .padding(.bottom, 8)
            .padding(.leading, 10)
            .background(
                RoundedRectangle(cornerRadius: 4, style: .continuous)
                    .stroke(Color(UIColor.systemGray5), lineWidth: 1)
            )
    }
}

I removed the init() and changed the Binding and var to @State variables for the purposes of testing as I don’t have any code that passes values to the View.

(edit) changed it to how you had it terms of the variable declarations but the init() is not required anyway as far as I can tell.

(edit 2)
Reverted back to @State variables

Hy Chris,

thank you very much! I have been looking for the @Binding property wrapper. I haven’t been aware, that you can pass var values through any kind of structs - especially when it is not according to the view protocol.

This helps a lot. But what I don’t understand is, why I don’t need the init() method. Friese explained, that this is necessary that we can use

TextInputField(“Email or User ID”, text: $email)

instead of

TextInputField(title: “Email or User ID”, text: $email)

,-) Peter

Yeah I am not sure why Friese took that viewpoint.

I have set up the variables as you did, ie:

    var title: String
    @Binding var text: String

and referenced the TextInputField in my ContentView in my test project and it all works nicely. The fact that the View in which TextInputField must be referenced like this

TextInputField(title: "Email or User ID", text: $text)

is pretty normal in SwiftUI. If you want to be able to remove the title: parameter then by all means use the init() method but in general that’s not normally done in SwiftUI.

My ContentView is as follows:

struct ContentView: View {
    @State var text: String = ""
    
    var body: some View {
        VStack {
            Text("Contents of text is: \(text)")
                .padding()
            TextInputField("Email or User ID", text: $text)
        }
    }
}

and the TextInputField and TextFieldStyle structs are as follows:

struct TextInputField: View {
    var title: String
    @Binding var text: String

    var body: some View {
        VStack {
            ZStack(alignment: .leading) {
                Text(title)
                    .padding(.leading, 10)
                    .foregroundColor(text.isEmpty ? Color(.placeholderText) : .accentColor)
                    .offset(y: text.isEmpty ? 0 : -15)
//                    .scaleEffect(text.isEmpty ? 1: 0.8, anchor .leading)
                TextField("", text: $text)
                    .textFieldStyle(MyTextFieldStyle(text: $text))
                    .autocapitalization(.none)
            }
            .padding(.horizontal)
        }
    }
}

struct MyTextFieldStyle: TextFieldStyle {
    @Binding var text: String
    func _body(configuration: TextField<Self._Label>) -> some View {
        configuration
            .padding(.top, text.isEmpty ? 8 : 25)
            .padding(.bottom, 8)
            .padding(.leading, 10)
            .background(
                RoundedRectangle(cornerRadius: 4, style: .continuous)
                    .stroke(Color(UIColor.systemGray5), lineWidth: 1)
            )
    }
}

I would be a little cautious about using a textField in this way as the change in it’s vertical height, as you begin to type, could be an issue in a UI in some cases especially where the view has a lot of elements in it.