Enable button in Swift only if all text fields are filled

Itโ€™s hard for me to understand how to change my code to make it so that the โ€œFinishโ€ button on the navigation bar turns on when my three text fields are populated.

I currently have three UITextFields and one UIButtonItem. Both habitNameField and targetField are manual text fields, and 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 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 changing the code so that it took two other fields as parameters and enabled doneBarButton only if all three fields were filled.

 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 does not work, even when I fill in all three text fields.

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

All code is 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 } 
+14
ios iphone uitextfield swift swift2
source share
10 answers

Xcode 9 โ€ข Swift 4

You can addTarget in your text fields to monitor the control event .editingChanged and use one selection 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 a selector method and use guard in combination with the where clause (Swift 3/4 uses a comma) to make sure that all text fields are not empty, otherwise just go back. Swift 3 does not require @objc, but Swift 4 requires:

 @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

+42
source share

Swift 3 / Xcode 8.2

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 a 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 } 

PS In the fast 3 " , where " == โ†’> " , "

+8
source share

The best way is to add an observer to the ViewDidLoad method. Instead of just checking the textField Delegate method, whether all text fields are populated or not. After he filled out the oberserver call method, and you just need to enable the button.

Note:

  • You can use the observer for the on or off button.

Hope this helps you.

+3
source share

I went ahead and distracted him a bit with a helper class that could be used for their quick 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 run the helper with

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

Make sure you keep a strong link on top to prevent release

 var buttonHelper: ButtonValidationHelper! 
+2
source share
 func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { if (textField == self.textField1) { /* do Something */ } else if (textField == self.textField2) { /* do Something */ } else if (textField == self.textField3) { /* do Something */ } // regardless of what you do before, doneBarButton is enabled when all are not empty doneBarButton.enabled = (textField1.length != 0) && (textField2.length != 0) && (textField3.length != 0) return true 

}

+1
source share

why not move the test functionality to a separate function

 func setDoneButtonStatus() { 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) } 

and then use

 func textFieldShouldReturn(textField: UITextField) -> Bool { textField.resignFirstResponder() setDoneButtonStatus() } 
+1
source share

This works for me: hope this helps

  func textFieldDidEndEditing(textField: UITextField) { if txtField1.hasText() && textField2.hasText() && textField3.hasText(){ doneBarButton.enabled = true } } 
+1
source share

You can create an array of text fields [UITextField] or a collection of output files. Let me call the array textFields or something like that.

 doneBarButton.isEnabled = !textFields.flatMap { $0.text?.isEmpty }.contains(true) 

And call the code above in the method that tracks the text field text change.

0
source share

Xcode 10.2 โ€ข Swift 4.3 version of Leo Dabus above.

This add user solution is probably the most common implementation for such a check.

 override func viewDidLoad() { super.viewDidLoad() addUserButton.backgroundColor = disabledButtonColor addUserButton.isEnabled = false [emailField, userNameField, firstNameField, lastNameField].forEach { (field) in field?.addTarget(self, action: #selector(editingChanged(_:)), for: .editingChanged) }} @objc private func editingChanged(_ textField: UITextField) { if textField.text?.count == 1 { if textField.text?.first == " " { textField.text = "" return } } guard let email = emailField.text, !email.isEmpty, let userName = userNameField.text, !userName.isEmpty, let firstName = firstNameField.text, !firstName.isEmpty, let lastName = lastNameField.text, !lastName.isEmpty else { addUserButton.isEnabled = false addUserButton.backgroundColor = disabledButtonColor return } addUserButton.isEnabled = true addUserButton.backgroundColor = activeButtonColor 

}

0
source share

With Combine (Xcode11 +, iOS13 +), this has become easier.

First you need to create a publisher for text changes:

 extension UITextField { var textPublisher: AnyPublisher<String, Never> { NotificationCenter.default .publisher(for: UITextField.textDidChangeNotification, object: self) .compactMap { $0.object as? UITextField } .map { $0.text ?? "" } .eraseToAnyPublisher() } } 

Then you can combine several publishers from several text fields:

 private var readyToLogIn: AnyPublisher<Bool, Never> { return Publishers .CombineLatest( emailTextField.textPublisher, passwordTextField.textPublisher ) .map { email, password in !email.isEmpty && !password.isEmpty } .eraseToAnyPublisher() } 

And then change the property of the isEnabled button based on this merged publisher

 readyToLogIn .receive(on: RunLoop.main) .assign(to: \.isEnabled, on: signInButton) 
0
source share

All Articles