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 cur
Xcode 10.2 • Swift 4.3 version of Leo Dabus above.
This solution is for adding a user, probably the most common implementation for such validation.
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
}
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!
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
With Combine (Xcode11+, iOS13+) this became easier.
First you need to be able 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 multiple publishers from multiple 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 your button isEnabled property based on that combined Publisher
readyToLogIn
.receive(on: RunLoop.main)
.assign(to: \.isEnabled, on: signInButton)
You can create an array of text fields [UITextField]
or an outlet collection. Let's call the array textFields
or something like that.
doneBarButton.isEnabled = !textFields.flatMap { $0.text?.isEmpty }.contains(true)
And call the code above in a method that monitors text field's text changes.
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
}