Enable a button in Swift only if all text fields have been filled out

28,201

Solution 1

Xcode 9 • Swift 4

You can addTarget to your text fields to monitor for the control event .editingChanged and use a single selector method for all of them:

override func viewDidLoad() {
    super.viewDidLoad()
    doneBarButton.isEnabled = false
    [habitNameField, goalField, frequencyField].forEach({ $0.addTarget(self, action: #selector(editingChanged), for: .editingChanged) })
}

Create the selector method and use guard combined with where clause (Swift 3/4 uses a comma) to make sure all text fields are not empty otherwise just return. Swift 3 does not require @objc, but Swift 4 does:

@objc func editingChanged(_ textField: UITextField) {
    if textField.text?.characters.count == 1 {
        if textField.text?.characters.first == " " {
            textField.text = ""
            return
        }
    }
    guard
        let habit = habitNameField.text, !habit.isEmpty,
        let goal = goalField.text, !goal.isEmpty,
        let frequency = frequencyField.text, !frequency.isEmpty
    else {
        doneBarButton.isEnabled = false
        return
    }
    doneBarButton.isEnabled = true
}

sample

Solution 2

Swift 5.1 /Xcode 11

enter image description here

override func viewDidLoad() {
    super.viewDidLoad()
    setupAddTargetIsNotEmptyTextFields()        
}

func setupAddTargetIsNotEmptyTextFields() {
    okButton.isHidden = true //hidden okButton
    nameUserTextField.addTarget(self, action: #selector(textFieldsIsNotEmpty), 
                                for: .editingChanged)
    emailUserTextField.addTarget(self, action: #selector(textFieldsIsNotEmpty), 
                                for: .editingChanged)
    passwordUserTextField.addTarget(self, action: #selector(textFieldsIsNotEmpty), 
                                for: .editingChanged)
    confimPasswordUserTextField.addTarget(self, action: #selector(textFieldsIsNotEmpty), 
                                for: .editingChanged)        
   }

and then create the selector method and use guard:

@objc func textFieldsIsNotEmpty(sender: UITextField) {

    sender.text = sender.text?.trimmingCharacters(in: .whitespaces)

    guard
      let name = nameUserTextField.text, !name.isEmpty,
      let email = emailUserTextField.text, !email.isEmpty,
      let password = passwordUserTextField.text, !password.isEmpty,
      let confirmPassword = confimPasswordUserTextField.text,
          password == confirmPassword          
      else
    {
      self.okButton.isHidden = true
      return
    }
    // enable okButton if all conditions are met
    okButton.isHidden = false
   }

Solution 3

The best way would be to Add observer in ViewDidLoad method. Than just check in textField Delegate method whether all TextFields are filled up or not. Once its filled up call oberserver method & in that you just need to enable button.

Note:

  • You can use observer for both enable or disable button

Hope it will help you.

Solution 4

I went ahead and abstracted this out a bit into a helper class that one can use for their swift project.

import Foundation
import UIKit

class ButtonValidationHelper {

  var textFields: [UITextField]!
  var buttons: [UIButton]!

  init(textFields: [UITextField], buttons: [UIButton]) {

    self.textFields = textFields
    self.buttons = buttons

    attachTargetsToTextFields()
    disableButtons()
    checkForEmptyFields()
  }

  //Attach editing changed listeners to all textfields passed in
  private func attachTargetsToTextFields() {
    for textfield in textFields{
        textfield.addTarget(self, action: #selector(textFieldsIsNotEmpty), for: .editingChanged)
    }
  }

  @objc private func textFieldsIsNotEmpty(sender: UITextField) {
    sender.text = sender.text?.trimmingCharacters(in: .whitespaces)
    checkForEmptyFields()
  }


  //Returns true if the field is empty, false if it not
  private func checkForEmptyFields() {

    for textField in textFields{
        guard let textFieldVar = textField.text, !textFieldVar.isEmpty else {
            disableButtons()
            return
        }
    }
    enableButtons()
  }

  private func enableButtons() {
    for button in buttons{
        button.isEnabled = true
    }
  }

  private func disableButtons() {
    for button in buttons{
        button.isEnabled = false
    }
  }

}

And then in your View Controller just simply init the helper with

buttonHelper = ButtonValidationHelper(textFields: [textfield1, textfield2, textfield3, textfield4], buttons: [button])

Make sure you keep a strong reference at top to prevent deallocation

var buttonHelper: ButtonValidationHelper!

Solution 5

This works for me: Hope it helps

  func textFieldDidEndEditing(textField: UITextField) {
        if txtField1.hasText() && textField2.hasText() && textField3.hasText(){
            doneBarButton.enabled = true
        }
    }
Share:
28,201

Related videos on Youtube

Derek Mei
Author by

Derek Mei

Hey, I'm Derek Mei. I'm a part-time graduate student pursuing my Master's in Human Factors in Information Design at Bentley University and a User Experience (UX) expert with 4 years of experience in the digital product space. Upon graduating from BU last year with degrees in math, business administration, and computer science, I've found a happy medium working in an industry I love while combining my creative and analytical mindset with my technical expertise. My strengths lie primarily in implementing high-level design thinking across different teams, helping bridge the gap between designers and developers, and optimizing the entire user experience all the way from conception to implementation. When I'm not working, you'll either find me playing basketball, writing about a variety of different topics on my blog, or trying new beers. If you want to work with me or just want to say hi, feel free to reach out to me! https://www.linkedin.com/in/derekmei/

Updated on November 19, 2021

Comments

  • Derek Mei
    Derek Mei over 2 years

    I am having trouble figuring out how to change my code to make it so the Done button in the navigation bar is enabled when my three text fields are filled out.

    I currently have three UITextFields and one UIButtonItem. Both the habitNameField and the goalField are manual text fields, and the frequencyField is a Picker View.

    @IBOutlet weak var habitNameField: UITextField!
    @IBOutlet weak var goalField: UITextField!
    @IBOutlet weak var frequencyField: UITextField!
    
    @IBOutlet weak var doneBarButton: UIBarButtonItem!
    

    I also have the following function that works when there is something typed in the first field.

    func textField(habitNameField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
        let oldHabitNameText: NSString = habitNameField.text!
        let newHabitNameText: NSString = oldHabitNameText.stringByReplacingCharactersInRange(range, withString: string)
        doneBarButton.enabled = (newHabitNameText.length != 0)
        return true
    }
    

    I tried change the code so that it took in the other two fields as parameters and enabled the doneBarButton only if all three fields were filled out.

    func textField(habitNameField: UITextField, goalField: UITextField, frequencyField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
        let habitNameText: NSString = (habitNameField.text!).stringByReplacingCharactersInRange(range, withString: string)
        let goalText: NSString = (goalField.text!).stringByReplacingCharactersInRange(range, withString: string)
        let frequencyText: NSString = (frequencyField.text!).stringByReplacingCharactersInRange(range, withString: string)
    
        doneBarButton.enabled = (habitNameText.length != 0) && (goalText.length != 0) && (frequencyText.length != 0)
        return true
    }
    

    However, it's not working, even when I fill out all three text fields.

    I would really appreciate any help, and thanks to anyone who contributes in advance!

    All code here:

    class HabitDetailViewController: UITableViewController, UITextFieldDelegate, UIPickerViewDataSource,UIPickerViewDelegate {
    @IBOutlet weak var habitNameField: UITextField!
    @IBOutlet weak var goalField: UITextField!
    @IBOutlet weak var doneBarButton: UIBarButtonItem!
    @IBOutlet weak var frequencyField: UITextField!
    
    var frequencies = ["Day", "Week", "Month", "Year"]
    var frequencyPicker = UIPickerView()
    
    var habitToEdit: HabitItem?
    weak var delegate: HabitDetailViewControllerDelegate?
    
    @IBAction func cancel() {
        delegate?.habitDetailViewControllerDidCancel(self)
    }
    
    @IBAction func done() {
        print("You plan to do \(habitNameField.text!) \(goalField.text!) times a \(frequencyField.text!.lowercaseString).")
        if let habit = habitToEdit {
            habit.name = habitNameField.text!
            habit.numberLeft = Int(goalField.text!)!
            habit.frequency = frequencyField.text!
            delegate?.habitDetailViewController(self, didFinishEditingHabit: habit)
        } else {
            let habit = HabitItem()
            habit.name = habitNameField.text!
            habit.numberLeft = Int(goalField.text!)!
            habit.frequency = frequencyField.text!
            habit.completed = false
            delegate?.habitDetailViewController(self, didFinishAddingHabit: habit)
        }
    }
    
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        habitNameField.becomeFirstResponder()
        frequencyPicker.hidden = false
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        frequencyPicker.dataSource = self
        frequencyPicker.delegate = self
        doneBarButton.enabled = false
        habitNameField.addTarget(self, action: "checkFields:", forControlEvents: .EditingChanged)
        goalField.addTarget(self, action: "checkFields:", forControlEvents: .EditingChanged)
        frequencyField.addTarget(self, action: "checkFields:", forControlEvents: .EditingChanged)
        frequencyField.inputView = frequencyPicker
        if let habit = habitToEdit {
            title = "Edit Item"
            habitNameField.text = habit.name
            goalField.text = String(habit.numberLeft)
            doneBarButton.enabled = true
        }
    }
    
    override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
        return nil
    }
    
    func textField(habitNameField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
        let oldHabitNameText: NSString = habitNameField.text!
        let newHabitNameText: NSString = oldHabitNameText.stringByReplacingCharactersInRange(range, withString: string)
        doneBarButton.enabled = (newHabitNameText.length != 0)
        return true
    }
    
    func checkFields(sender: UITextField) {
        sender.text = sender.text?.stringByTrimmingCharactersInSet(.whitespaceCharacterSet())
        guard
            let habit = habitNameField.text where !habit.isEmpty,
            let goal = goalField.text where !goal.isEmpty,
            let frequency = frequencyField.text where !frequency.isEmpty
            else { return }
        // enable your button if all conditions are met
        doneBarButton.enabled = true
    }
    
  • Derek Mei
    Derek Mei over 8 years
    This is working, but for some reason, it works even if only of the fields is filled in. Why is that?
  • Derek Mei
    Derek Mei over 8 years
    Durr. That's a rookie mistake on my part. Thanks for helping me catch that. Also, the final text field for frequency isn't working because it's a picker view. Any ideas on that?
  • Leo Dabus
    Leo Dabus over 8 years
    You can create a boolean var to monitor it and flag it once the user sets it. Then just add it to the guard statement
  • Bradley
    Bradley over 7 years
    Should be the correct answer, it works fine for me, the delegate method doesn't provide something like .editingChanged
  • Leo Dabus
    Leo Dabus about 7 years
    Note that this is only available for iOS (10.0 and later), tvOS (10.0 and later). BTW it is not a method anymore. Now it is a computed property
  • Prabhav
    Prabhav over 6 years
    Great solution! You need to mark the 'textFieldsIsNotEmpty' as '@objc' else the compiler throws an error. Please update the answer.
  • karthikeyan
    karthikeyan about 6 years
    how to check email id and not empty for single textfield, tried this but not working let emaiTextField = existingEmailAddressTextField.text, !emaiTextField.isEmpty, let validEmail = existingEmailAddressTextField.text, self.validateEmail(validEmail)==true,
  • karthikeyan
    karthikeyan about 6 years
    how to check valid email id or not for same text field
  • karthikeyan
    karthikeyan about 6 years
    how to check valid email id or not and empty for same text field
  • L33MUR
    L33MUR almost 3 years
    Could you give an example of how to add the observer? Thx!