TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: How to check a block of text fields for emptiness on button click? 🙄

Forums > SwiftUI

Good afternoon everyone. I have a block of text fields combined with an array of structures. And it is possible to check the completion of the fields not immediately but by clicking a button. When clicked, it will be displayed which fields are not filled in.

There is no idea of implementation at all. 🙄

struct CardTourist: View {

    @State var isShow: Bool = false
    @Binding var currentTourist: TouristCard

    let title: String

    var body: some View {
        VStack(spacing: 0) {
            ShowCardTourist(isShow: $isShow, title: title, action: {
                DispatchQueue.main.async {
                    withAnimation(.easeInOut) {
                        self.isShow.toggle()

                        // Click for check text field
                    }
                }
            })
            .padding(.bottom, isShow ? 17 : 0)
            if self.isShow {
                VStack(spacing: 8) {
                    TextFieldForTouristWithPlaceholder(textField: $currentTourist.name,
                                                       title: "Имя")
                    TextFieldForTouristWithPlaceholder(textField: $currentTourist.lastName,
                                                       title: "Фамилия")
                    TextFieldForTourist(textField: $currentTourist.dateBirth,
                                        pleaceHolder: "Дата рождения",
                                        type: .numberPad)
                    TextFieldForTourist(textField: $currentTourist.citizenShip,
                                        pleaceHolder: "Гражданство")
                    TextFieldForTourist(textField: $currentTourist.numberPassport,
                                        pleaceHolder: "Номер загранпаспорта")
                    TextFieldForTourist(textField: $currentTourist.validityPeriodPassport,
                                        pleaceHolder: "Срок действия загранпаспорта",
                                        type: .numberPad)
                }
            }
        }
        .padding(.horizontal, 13)
        .vLeading()
        .padding(.vertical, 16)
        .background(Color.white)
        .cornerRadius(15)
    }
}
struct TextFieldForTourist: View {

    @Binding var textField: String
    @State var isValid: Bool = false

    let pleaceHolder: String
    var type: UIKeyboardType = .default

    var body: some View {
        ZStack(alignment: .leading) {
            if textField.isEmpty {
                Text(pleaceHolder)
                    .modifier(HeightModifier(size: 17,
                                             lineHeight: 110,
                                             weight: .regular))
                    .foregroundColor(.c_A9ABB7)
            }
            TextField("", text: $textField, onEditingChanged: { _ in
//                if textField.isEmpty {
//                    self.isValid = true
//                } else {
//                    self.isValid = false
//                }
            })
                .modifier(HeightModifier(size: 17,
                                         lineHeight: 110,
                                         weight: .regular))
                .tracking(0.1)
                .foregroundColor(.c_14142B)
                .tint(.black)
                .keyboardType(type)
        }
        .vLeadingAndBack(isValid)
    }
}

2      

Hi Steven, for that purpose there is @FocuseState wrapper is used. I will post example code in a while, meantime you may want to read about it.

2      

As promised. You can have a look how to monitor all those textfields and apply your logic in your project.

struct ContentView: View {
    // Create enum to monitor focus state of text fields
    enum NameFields {
        case firstName
        case lastName
    }

    @State private var firstName = ""
    @State private var lastName = ""

    // This is just to show on the screen what was submitted
    @State private var showSubmittedNames = false

    // Monitor focused state of text fields using this property
    @FocusState private var nameFields: NameFields?

    var body: some View {
        VStack(spacing: 20) {
            TextField("First Name", text: $firstName)
                .focused($nameFields, equals: .firstName)

            TextField("Last Name", text: $lastName)
                .focused($nameFields, equals: .lastName)

            Button("Submit") {
                if firstName.isEmpty {
                    nameFields = .firstName
                } else if lastName.isEmpty {
                    nameFields = .lastName
                } else {
                    // save function or whatever logic applied when all fields are filled
                    // here we just toggle to show the names on the screen
                    showSubmittedNames = true
                }
            }
            // if you want to disable your button unless all fields are filled in
            // you may want to monitor its disabled status
            .disabled(firstName.isEmpty || lastName.isEmpty)
            // with this you can change return button name on virtual keyboard
            // other options also available like 'go', 'join', 'next' etc
            .submitLabel(.done)

            // This is just to show that data was submitted
            if showSubmittedNames {
                Text(firstName + " " + lastName)
            } else {
                EmptyView()
            }
        }
        .padding()
        .textFieldStyle(.roundedBorder)
        .onSubmit {
            // once you tap return on virtual keyboard this will be triggered
            showSubmittedNames = true
        }
    }
}

PS. If you comment out .disabled(firstName.isEmpty || lastName.isEmpty) after pressing submit button the cursor will be blinking in the textField that is empty. This is what I guess you are trying to achieve.

3      

Hacking with Swift is sponsored by String Catalog.

SPONSORED Get accurate app localizations in minutes using AI. Choose your languages & receive translations for 40+ markets!

Localize My App

Sponsor Hacking with Swift and reach the world's largest Swift community!

Good afternoon, Mr. @ygeras, very grateful for the help I'm trying to change the background of a text field if the field is empty. Check after pressing the button.

I’m thinking about how to transfer the check to a higher level. Since this is an array of cards with text fields.

I'm still trying to figure out this method of transferring data.

Calling Method on Child View in SwiftUI

video

struct CardTourist: View {

    @State var isShow: Bool = false

    @Binding var currentTourist: TouristCard

    let title: String

    var testView: TextFieldForTourist  {
        TextFieldForTourist(textField: $currentTourist.validityPeriodPassport, pleaceHolder: "Срок действия загранпаспорта")
    }
    var body: some View {
        VStack(spacing: 0) {
            ShowCardTourist(isShow: $isShow, title: title, action: {
                DispatchQueue.main.async {
                    withAnimation(.easeInOut) {
                        self.isShow.toggle()
                    }
                }
            })
            .padding(.bottom, isShow ? 17 : 0)
            if self.isShow {
                VStack(spacing: 8) {
                    TextFieldForTouristWithPlaceholder(textField: $currentTourist.name,
                                                       title: "Имя")
                    TextFieldForTouristWithPlaceholder(textField: $currentTourist.lastName,
                                                       title: "Фамилия")
                    TextFieldForTourist(textField: $currentTourist.dateBirth,
                                        pleaceHolder: "Дата рождения",
                                        type: .numberPad)
                    TextFieldForTourist(textField: $currentTourist.citizenShip,
                                        pleaceHolder: "Гражданство")
                    TextFieldForTourist(textField: $currentTourist.numberPassport,
                                        pleaceHolder: "Номер загранпаспорта")
                    testView
                    //TextFieldForTourist(textField: $currentTourist.validityPeriodPassport,
                                      //  pleaceHolder: "Срок действия загранпаспорта",
                                      //  type: .numberPad)
                }
            }
        }
        .padding(.horizontal, 13)
        .vLeading()
        .padding(.vertical, 16)
        .background(Color.white)
        .cornerRadius(15)
    }
}
struct TextFieldForTourist: View {

    @Binding var textField: String
    @State var isValid: Bool = false

    let pleaceHolder: String
    var type: UIKeyboardType = .default

    var body: some View {
        ZStack(alignment: .leading) {
            if textField.isEmpty {
                Text(pleaceHolder)
                    .modifier(HeightModifier(size: 17,
                                             lineHeight: 110,
                                             weight: .regular))
                    .foregroundColor(.c_A9ABB7)
            }
            HStack {
                TextField("", text: $textField)
                    .modifier(HeightModifier(size: 17,
                                             lineHeight: 110,
                                             weight: .regular))
                    .tracking(0.1)
                    .foregroundColor(.c_14142B)
                    .tint(.black)
                    .keyboardType(type)
                Circle()
                    .fill(isValid ? Color.green : Color.red)
                    .frame(width: 10, height: 10)
            }
        }
        .vLeadingAndBack(isValid)
    }

    func checkEmpty() {
        if !textField.isEmpty {
            self.isValid = true
        }
    }
}

2      

I know you have some logic behind your code, but without have more details on how data is passed around it is diffucult to conclude how you handle it behind the scenes.

Try to paste the below code and have a look at the implementation. Maybe you could use some kind of the same approach. Seem like should work in your case if implemented wisely.

// Create enum to monitor focus state of text fields
enum NameFields: String {
    case firstName = "First Name"
    case lastName = "Last Name"
    case passportNumber = "Passport Number"
}

struct ContentView: View {
    @State private var firstName = ""
    @State private var lastName = ""
    @State private var passportNumber = ""

    // Monitor focused state of text fields using this property
    @FocusState private var nameFields: NameFields?

    @State private var submitPressed = false

    // This is just to show on the screen what was submitted
    @State private var showSubmittedNames = false

    var body: some View {
        VStack(spacing: 20) {
            CustomTextField(text: $firstName, focus: $nameFields, nameField: .firstName)
                .overlay(
                    RoundedRectangle(cornerRadius: 8)
                        .stroke(firstName.isEmpty && submitPressed ? Color.red : Color.clear, lineWidth: 2)
                )

            CustomTextField(text: $lastName, focus: $nameFields, nameField: .lastName)
                .overlay(
                    RoundedRectangle(cornerRadius: 8)
                        .stroke(lastName.isEmpty && submitPressed ? Color.red : Color.clear, lineWidth: 2)
                )

            CustomTextField(text: $passportNumber, focus: $nameFields, nameField: .passportNumber)
                .overlay(
                    RoundedRectangle(cornerRadius: 8)
                        .stroke(passportNumber.isEmpty && submitPressed ? Color.red : Color.clear, lineWidth: 2)
                )

            Button("Submit") {
                if firstName.isEmpty {
                    nameFields = .firstName
                } else if lastName.isEmpty {
                    nameFields = .lastName
                } else if passportNumber.isEmpty {
                    nameFields = .passportNumber
                } else {
                    // save function or whatever logic applied when all fields are filled
                    // here we just toggle to show the names on the screen
                    showSubmittedNames = true
                    nameFields = nil
                }

                submitPressed = true

            }
            // with this you can change return button name on virtual keyboard
            // other options also available like 'go', 'join', 'next' etc
            .submitLabel(.done)

            // This is just to show that data was submitted
            if showSubmittedNames {
                Text(firstName + " " + lastName + " " + passportNumber)
            } else {
                EmptyView()
            }
        }
        .padding()
        .textFieldStyle(.roundedBorder)
        .onSubmit {
            // once you tap return on virtual keyboard this will be triggered
            showSubmittedNames = true
            submitPressed = true
        }
    }
}

struct CustomTextField: View {
    @Binding var text: String
    @FocusState.Binding var focus: NameFields?
    var nameField: NameFields

    var body: some View {
        TextField("\(nameField.rawValue)", text: $text)
            .focused($focus, equals: nameField)
    }
}

3      

Another option to try using Combine. As you can see all checks are done in your viewModel.

class ViewModel: ObservableObject {
    @Published var firstName = ""
    @Published var lastName = ""
    @Published var passportNumber = ""

    @Published var nameValidation = ""
    @Published var lastNameValidation = ""
    @Published var passportValidation = ""

    init() {
        $firstName
            .map {$0.isEmpty ? "🔴" : "🟢"}
            .assign(to: &$nameValidation)

        $lastName
            .map {$0.isEmpty ? "🔴" : "🟢"}
            .assign(to: &$lastNameValidation)

        $passportNumber
            .map {$0.isEmpty ? "🔴" : "🟢"}
            .assign(to: &$passportValidation)
    }
}

// Create enum to monitor focus state of text fields
enum NameFields: String {
    case firstName = "First Name"
    case lastName = "Last Name"
    case passportNumber = "Passport Number"
}

struct ContentView: View {
    @StateObject private var vm = ViewModel()

    // Monitor focused state of text fields using this property
    @FocusState private var nameFields: NameFields?

    @State private var submitPressed = false

    // This is just to show on the screen what was submitted
    @State private var showSubmittedNames = false

    var body: some View {
        VStack(spacing: 20) {
            CustomTextField(text: $vm.firstName,
                            validation: $vm.nameValidation,
                            focus: $nameFields,
                            nameField: .firstName,
                            submitPressed: submitPressed
            )

            CustomTextField(text: $vm.lastName,
                            validation: $vm.lastNameValidation,
                            focus: $nameFields,
                            nameField: .lastName,
                            submitPressed: submitPressed
            )

            CustomTextField(text: $vm.passportNumber,
                            validation: $vm.passportValidation,
                            focus: $nameFields,
                            nameField: .passportNumber,
                            submitPressed: submitPressed
            )

            Button("Submit") {
                if vm.firstName.isEmpty {
                    nameFields = .firstName
                } else if vm.lastName.isEmpty {
                    nameFields = .lastName
                } else if vm.passportNumber.isEmpty {
                    nameFields = .passportNumber
                } else {
                    // save function or whatever logic applied when all fields are filled
                    // here we just toggle to show the names on the screen
                    showSubmittedNames = true
                    nameFields = nil
                }

                submitPressed = true

            }
            // with this you can change return button name on virtual keyboard
            // other options also available like 'go', 'join', 'next' etc
            .submitLabel(.done)

            // This is just to show that data was submitted
            if showSubmittedNames {
                Text(vm.firstName + " " + vm.lastName + " " + vm.passportNumber)
            } else {
                EmptyView()
            }
        }
        .padding()
        .onSubmit {
            // once you tap return on virtual keyboard this will be triggered
            showSubmittedNames = true
            submitPressed = true
        }
    }
}

struct CustomTextField: View {
    @Binding var text: String
    @Binding var validation: String

    @FocusState.Binding var focus: NameFields?
    var nameField: NameFields

    var submitPressed: Bool

    var body: some View {
        ZStack(alignment: .trailing) {
            TextField("\(nameField.rawValue)", text: $text)
                .padding(8)
                .focused($focus, equals: nameField)
                .background(
                    RoundedRectangle(cornerRadius: 8)
                        .fill(text.isEmpty && submitPressed ? Color.red.opacity(0.5) : Color.clear)
                        .strokeBorder(Color.gray.opacity(0.2))
                )

            Text(validation)
                .font(.caption2)
                .padding(.trailing, 8)
        }
    }
}

3      

Hi Mr. @ygeras! Really appreciate your time and help! Posted the file on git with example code. This is link GitHub Files with example code - CardTourist and BookingView

Now I’ll try to do in After Effects what I would like to implement.

Video example

Your Mr. @ygeras code works great! I'll try to adapt it to suit myself.

All I have to do is read about the @Publisher, otherwise it turns out I don’t know a lot.

2      

Everything worked out Mr. @ygeras 🥳 I just looped through the array and used your example code!

2      

Glad to hear that Steven! Just small note. For proper work and to avoidance of memory leak, it is better to modify code slightly for Combine way appropriate. This is has to do with pipeline still working and publishing data after the class is deinitialized. I know that might sound compicated, but it is really that a bit complex. Even myself not so super proficient in Combine. Consider to update to this.

// add this import
import Combine

class ViewModel: ObservableObject {
    @Published var firstName = ""
    @Published var lastName = ""
    @Published var passportNumber = ""

    @Published var nameValidation = ""
    @Published var lastNameValidation = ""
    @Published var passportValidation = ""

    private var cancellables: Set<AnyCancellable> = []

    init() {
        $firstName
            .map {$0.isEmpty ? "🔴" : "🟢"}
            // UPDATE
            .sink { [unowned self] value in
                self.nameValidation = value
            }
            .store(in: &cancellables)
            // END OF UPDATE

        $lastName
            .map {$0.isEmpty ? "🔴" : "🟢"}
            // UPDATE
            .sink { [unowned self] value in
                self.lastNameValidation = value
            }
            .store(in: &cancellables)
            // END OF UPDATE

        $passportNumber
            .map {$0.isEmpty ? "🔴" : "🟢"}
            // UPDATE
            .sink { [unowned self] value in
                self.passportValidation = value
            }
            .store(in: &cancellables)
            // END OF UPDATE
    }
}

The code still continues to work as before.

2      

Hi, Mr. @ygeras!

 private var cancellables: Set<AnyCancellable> = []

    init() {
      $firstName
        .map {$0.isEmpty ? "🔴" : "🟢"}

        .sink { [unowned self] value in
            self.nameValidation = value
        }
      .store(in: &cancellables)
    }
  .sink     // ".sink", as I understand it, it’s something like an observer who looks at the last result of the change. 👂🏼
  private var cancellables: Set<AnyCancellable> = []   // "cancellables" is a protocol that  saves in cancelables, and clears away work 🤐

  $firstName
        .map {$0.isEmpty ? "🔴" : "🟢"}

Every publisher needs to be closed to avoid memory leaks?

2      

Some interesting error, the function works twice?

Video with error

2      

Hacking with Swift is sponsored by String Catalog.

SPONSORED Get accurate app localizations in minutes using AI. Choose your languages & receive translations for 40+ markets!

Localize My App

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.