Reputation: 841
I have an action
that creates an alert
when the button is tapped.
This alert
consists of two textfields
and two actions
(add
and dismiss
).
Is there any simple way for me to disable the add
button if the textfields
are empty (I'll later check if the second one is really an URL but let's keep things focused on the action
here)?
Here is my code and what I tried so far:
@IBAction func addSourceTapped(_ sender: Any) {
let alert = UIAlertController(title: "Add a News Source", message: nil, preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = "Name"
}
alert.addTextField { (textField) in
textField.placeholder = "Link"
}
let name = alert.textFields!.first!.text!
let link = alert.textFields!.last!.text!
if name != "" && link != "" {
alert.actions[0].isEnabled = true
}
let action = UIAlertAction(title: "Add", style: .default) { (_) in
let name = alert.textFields!.first!.text!
let link = alert.textFields!.last!.text!
if name != "" && link != "" {
alert.actions[0].isEnabled = true //doesn't do anything yet..
if CoreDataHandler.saveSource(name: "\(name)", link: "\(link)") {
for _ in CoreDataHandler.fetchSource()! {
}
}
}
self.tableView.reloadData()
}
let action2 = UIAlertAction(title: "Dismiss", style: .default)
alert.addAction(action)
alert.actions[0].isEnabled = false
alert.addAction(action2)
present(alert, animated: true, completion: nil)
}
Upvotes: 3
Views: 2412
Reputation: 513
Programmatic solution with the usage of the actions. Add button to class and continue to change its status according to the change made in the text field.
import UIKit
final class ViewWithConfirmationController: UIViewController {
private let askButton = UIButton()
private let deleteAction = UIAlertAction(
title: "Confirm",
style: .default
) { _ in
// Confirmed
}
init() {
super.init(nibName: nil, bundle: nil)
view.addSubview(askButton)
askButton.setTitle("Call alert", for: .normal)
askButton.addTarget(
self,
action: #selector(callAlert),
for: .touchUpInside
)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func callAlert() {
let alert = UIAlertController(
title: "Confirmation alert",
message: """
Do you really want to do that?
Please type in the magic word to confirm.
""",
preferredStyle: .alert
)
alert.addTextField { textField in
textField.placeholder = "Magic word"
textField.addTarget(
self,
action: #selector(self.textFieldDidChange(_:)),
for: .editingChanged
)
}
alert.addAction(
UIAlertAction(
title: "Cancel",
style: .cancel,
handler: { _ in }
))
alert.addAction(deleteAction)
// Confirm button will disabled until user type current organisation name
deleteAction.isEnabled = false
present(alert, animated: true, completion: nil)
}
@objc private func textFieldDidChange(_ field: UITextField) {
guard let text = field.text else {
print("Can't get text from text field in \(#function)")
return
}
deleteAction.isEnabled = !text.isEmpty
}
}
Upvotes: 1
Reputation: 151
You should better use actions, it's simpler than the other answers. If you try to use delegate, methods didBeginEditing & didEndEditing will not handle it correctly. Neither do shouldChangeCharactersIn range, it performs before the input occurs in textfield, so it's not correct to check textField's text property there.
private var action: UIAlertAction!
@IBAction func addSourceTapped(_ sender: Any) {
////////
let alert = UIAlertController(title: "Add a News Source", message: nil, preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = "Name"
textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged) // <---
}
action = UIAlertAction(title: "Add", style: .default) { (_) in
/////
}
action.isEnabled = false
alert.addAction(action)
////////
}
@objc private func textFieldDidChange(_ field: UITextField) {
action.isEnabled = field.text?.count ?? 0 > 0
}
Upvotes: 7
Reputation: 2120
You can't write UITextField validating code (Code to enable/ disable Action) any where!!. This Validation must be done each time UItextfield update. So for doing that you need to get the delegate from UITextField for its updates
You need to save the textfield globally and assign delegate of textfields to your ViewController. By doing that you can code for validating code for textfields in your UIViewController and programmatically Enable or disable your actions
class YOUR_CLASS_NAME:UIViewController,UITextFieldDelegate {
var alert: UIAlertController!
var action: UIAlertAction!
var action2: UIAlertAction!
var nameTextFeld:UITextField!
var linkTextFeld:UITextField!
@IBAction func addSourceTapped(_ sender: Any) {
alert = UIAlertController(title: "Add a News Source", message: nil, preferredStyle: .alert)
alert.addTextField { (textField) in
self.nameTextFeld = textField
self.nameTextFeld.placeholder = "Name"
self.nameTextFeld.delegate = self
}
alert.addTextField { (textField) in
self.linkTextFeld = textField
self.linkTextFeld.placeholder = "Link"
self.linkTextFeld.delegate = self
}
action = UIAlertAction(title: "Add", style: .default) { (_) in
let name = self.alert.textFields!.first!.text!
let link = self.alert.textFields!.last!.text!
if CoreDataHandler.saveSource(name: "\(name)", link: "\(link)") {
for _ in CoreDataHandler.fetchSource()! {
}
}
self.tableView.reloadData()
}
action.isEnabled = false
action2 = UIAlertAction(title: "Dismiss", style: .default)
alert.addAction(action)
alert.actions[0].isEnabled = false
alert.addAction(action2)
present(alert, animated: true, completion: nil)
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if !((nameTextFeld.text?.isEmpty)! && (linkTextFeld.text?.isEmpty)!) {
action.isEnabled = true
}
return true
}
}
Upvotes: 4
Reputation: 131
Do that by configuring the text field when adding it
alert.addTextField(configurationHandler: { (textField) -> Void in
textField.placeholder = "Name"
// Other configuration such as textField.autocapitalizationType = .words
NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextFieldTextDidChange, object: textField, queue: OperationQueue.main) { (notification) in
let nameField = alert.textFields![0] as UITextField
let linkField = alert.textFields![1] as UITextField
action.isEnabled = self.getTextFieldContent(nameField) != "" && self.getTextFieldContent(linkField) != ""
}
})
You need to do this separately for both text fields (though you could do that by reusing the same code). Note also, that if you reference the textfields directly in the action's handler block, it will create a strong reference cycle and your controller will not be deinited. To avoid that do the reference to the text field with a local nullable variable like this
var tf: UITextField?
let action = UIAlertAction(title: "Add", style: .default) { (_) in
guard let textField = tf else {
return;
}
let name = textField.text!
// Rest the action here
}
alertController.addTextField { (textField) in
// Configure textField here
tf = textField
}
Upvotes: 2