Wizzardzz
Wizzardzz

Reputation: 841

How to disable UIAlertAction depending on UITextField?

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

Answers (4)

Matvii Hodovaniuk
Matvii Hodovaniuk

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

Ilya Kuznetsov
Ilya Kuznetsov

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

Britto Thomas
Britto Thomas

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

Pomo-J
Pomo-J

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

Related Questions