Enable a button in Swift only if all text fields have been filled out
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
}
Solution 2
Swift 5.1 /Xcode 11
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
}
}
Related videos on Youtube
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, 2021Comments
-
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 over 8 yearsThis is working, but for some reason, it works even if only of the fields is filled in. Why is that?
-
Derek Mei over 8 yearsDurr. 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 over 8 yearsYou 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 over 7 yearsShould be the correct answer, it works fine for me, the delegate method doesn't provide something like .editingChanged
-
Leo Dabus about 7 yearsNote 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 over 6 yearsGreat solution! You need to mark the 'textFieldsIsNotEmpty' as '@objc' else the compiler throws an error. Please update the answer.
-
karthikeyan about 6 yearshow 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 about 6 yearshow to check valid email id or not for same text field
-
karthikeyan about 6 yearshow to check valid email id or not and empty for same text field
-
L33MUR almost 3 yearsCould you give an example of how to add the observer? Thx!