swiftswiftuitextboxtextinput

Can we add an image inside a textbox without the use of a stack in swiftui?


I am trying to add an image into my textinput in swiftui without the use of a zstack because this cause the whole view to bug, this is the expected result , is there a way to add them without the use of a stack? or should i adapt the whole code based on it ?

                                HStack {
                                    ZStack {
                                        CustomTextField(placeholder: "Email", text: $email, textColor: .black, isValid: emailValid)
                                            .focused($isEmailFocused)
                                        
                                        if let isValid = emailValid {
                                            Image(isValid ? "check_solid" : "cancel_solid")
                                                .resizable()
                                                .frame(width: 25, height: 25)
                                                .foregroundColor(isValid ? Color("greenstrokes") : Color("redstrokes"))
                                                .position(x: UIScreen.main.bounds.width - 100, y: 75)
                                        }
                                    }
                                }

Solution

  • While the overlay is the way to go for placing and aligning the image over the textfield, you should consider creating a view modifier for validation, so you can apply it to any textfield.

    Otherwise, you will have repeated code and overlays everywhere, if you have multiple fields.

    Here's a rough example that uses a view extension and a view modifier:

    import SwiftUI
    
    struct ValidateTextFieldView: View {
        
        @State private var inputText: String = ""
        @State private var inputNumber: Int?
        
        var body: some View {
            VStack(spacing: 10) {
                TextField("Type yes to confirm", text: $inputText)
                    .padding()
                    .background(.white)
                    .clipShape(RoundedRectangle(cornerRadius: 8))
                    .validateTextField($inputText)
                    .textInputAutocapitalization(.never)
                
                Text("Press Enter to validate field")
                    .font(.caption)
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .foregroundStyle(.secondary)
                    .padding(.leading)
            }
            .padding()
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
            .background(.background.secondary)
        }
    }
    
    extension View {
        
        func validateTextField(_ text: Binding<String>) -> some View {
            self
                .modifier(ValidateTextFieldModifier(text: text))
        }
    }
    
    struct ValidateTextFieldModifier: ViewModifier {
        
        //Parameters
        @Binding var text: String
        
        //State values
        @State private var isValid: Bool = false
        @State private var showValidation: Bool = false
        @State private var validationColor: Color = .clear
        
        //Body
        func body(content: Content) -> some View {
            content
                .onChange(of: text) {
                    validationColor = .yellow
                }
                .onSubmit {
                    if text == "yes" { //add your own validation logic
                        validationColor = .green
                    }
                    else { //if not valid
                        validationColor = .red
                    }
                }
                .overlay(
                    RoundedRectangle(cornerRadius: 8) // Rounded rectangle with a border
                        .stroke(validationColor, lineWidth: 2)
                )
                .overlay(alignment: . trailing) {
                    Image(systemName: validationColor == .green ? "checkmark.circle.fill" : validationColor == .red ? "xmark.circle.fill" :  "")
                            .foregroundStyle(validationColor)
                            .padding(.trailing)
                }
        }
    }
    
    #Preview {
        ValidateTextFieldView()
    }
    

    I used a state to set the color depending on the validation result, but normally, you'd have a more refined method, like maybe using an enum for validation state, additional validation functions, maybe more parameters, etc.