30

I have an AlertController with a text field and two button: CANCEL and SAVE. This is the code:

@IBAction func addTherapy(sender: AnyObject)
{
    let addAlertView = UIAlertController(title: "New Prescription", message: "Insert a name for this prescription", preferredStyle: UIAlertControllerStyle.Alert)

    addAlertView.addAction(UIAlertAction(title: "Cancel",
                                         style: UIAlertActionStyle.Default,
                                         handler: nil))

    addAlertView.addAction(UIAlertAction(title: "Save",
                                         style: UIAlertActionStyle.Default,
                                         handler: nil))

    addAlertView.addTextFieldWithConfigurationHandler({textField in textField.placeholder = "Title"})


    self.presentViewController(addAlertView, animated: true, completion: nil)


}

What I want to do is implement a check on the textfield for disabling the SAVE button when the textfield is empty just like Pictures Application of iOS when you want create a NewAlbum. Please someone can explain me what to do?

Larme
  • 24,190
  • 6
  • 51
  • 81
msalafia
  • 2,703
  • 5
  • 23
  • 34

4 Answers4

57

There is a much simpler way without using notification center, in swift:

weak var actionToEnable : UIAlertAction?

func showAlert()
{
    let titleStr = "title"
    let messageStr = "message"

    let alert = UIAlertController(title: titleStr, message: messageStr, preferredStyle: UIAlertControllerStyle.alert)

    let placeholderStr =  "placeholder"

    alert.addTextField(configurationHandler: {(textField: UITextField) in
        textField.placeholder = placeholderStr
        textField.addTarget(self, action: #selector(self.textChanged(_:)), for: .editingChanged)
    })

    let cancel = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: { (_) -> Void in

    })

    let action = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: { (_) -> Void in
        let textfield = alert.textFields!.first!

        //Do what you want with the textfield!
    })

    alert.addAction(cancel)
    alert.addAction(action)

    self.actionToEnable = action
    action.isEnabled = false
    self.present(alert, animated: true, completion: nil)
}

func textChanged(_ sender:UITextField) {
    self.actionToEnable?.isEnabled  = (sender.text! == "Validation")
}
ullstrm
  • 9,812
  • 7
  • 52
  • 83
  • 1
    On the Swift 3 code, `actionToEnable` variable defintion is missing – Maxi Mus Apr 07 '17 at 08:27
  • Superb..! Cool Answer – Jay Patel Apr 27 '17 at 08:27
  • Swift 5 changed the functions quite a bit. Xcode at least suggests the edits, apart from one thing: `#selector(self.textChanged(_:))` doesn't work like that anymore, it just gives an error (`Type 'MyClass' has no member 'textChanged'`) - yes, the function is there. I tried all the versions suggested [here](https://stackoverflow.com/a/41049859/2016165) but nothing worked. – Neph May 24 '23 at 10:56
28

I would first create the alertcontroller with the save action initially disabled. Then when adding the textfield inculde a Notification to observe its change in the handler and in that selector just toggle the save actions enabled property.

Here is what I am saying:

//hold this reference in your class
weak var AddAlertSaveAction: UIAlertAction?

@IBAction func addTherapy(sender : AnyObject) {

    //set up the alertcontroller
    let title = NSLocalizedString("New Prescription", comment: "")
    let message = NSLocalizedString("Insert a name for this prescription.", comment: "")
    let cancelButtonTitle = NSLocalizedString("Cancel", comment: "")
    let otherButtonTitle = NSLocalizedString("Save", comment: "")

    let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)

    // Add the text field with handler
    alertController.addTextFieldWithConfigurationHandler { textField in
        //listen for changes
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleTextFieldTextDidChangeNotification:", name: UITextFieldTextDidChangeNotification, object: textField)
    }


    func removeTextFieldObserver() {
        NSNotificationCenter.defaultCenter().removeObserver(self, name: UITextFieldTextDidChangeNotification, object: alertController.textFields[0])
    }

    // Create the actions.
    let cancelAction = UIAlertAction(title: cancelButtonTitle, style: .Cancel) { action in
        NSLog("Cancel Button Pressed")
        removeTextFieldObserver()
    }

    let otherAction = UIAlertAction(title: otherButtonTitle, style: .Default) { action in
        NSLog("Save Button Pressed")
        removeTextFieldObserver()
    }

    // disable the 'save' button (otherAction) initially
    otherAction.enabled = false

    // save the other action to toggle the enabled/disabled state when the text changed.
    AddAlertSaveAction = otherAction

    // Add the actions.
    alertController.addAction(cancelAction)
    alertController.addAction(otherAction)

    presentViewController(alertController, animated: true, completion: nil)
} 

    //handler
func handleTextFieldTextDidChangeNotification(notification: NSNotification) {
    let textField = notification.object as UITextField

    // Enforce a minimum length of >= 1 for secure text alerts.
    AddAlertSaveAction!.enabled = textField.text.utf16count >= 1
}

I am doing this in another project - I got this pattern directly from apple examples. They have a very good example project outlining a few of these patterns in the UICatalog examples: https://developer.apple.com/library/content/samplecode/UICatalog/Introduction/Intro.html

Cœur
  • 37,241
  • 25
  • 195
  • 267
Rufus
  • 2,058
  • 2
  • 16
  • 10
  • You help me a lot! Thank you very much! – msalafia Jun 30 '14 at 09:49
  • 4
    Using a notification is way too heavy-handed (and unreliable, and too much work). This is why there are text field delegates and actions. See my answer here: http://stackoverflow.com/a/25628065/341994 – matt Sep 02 '14 at 18:01
  • 1
    Matt's answer is better – LastMove Nov 15 '15 at 23:39
  • It is giving error: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Naina.SearchVillageTableViewController handleTextFieldTextDidChangeNotification:]: unrecognized selector sent to instance 0x7f802dc41f50' – Pawan Sep 11 '18 at 13:25
3

Swift 3.0 Updated Solution given By @spoek

func showAlert()
    {
        let titleStr = "title"
        let messageStr = "message"

        let alert = UIAlertController(title: titleStr, message: messageStr, preferredStyle: UIAlertControllerStyle.alert)

        let placeholderStr =  "placeholder"

        alert.addTextField(configurationHandler: {(textField: UITextField) in
            textField.placeholder = placeholderStr
            textField.addTarget(self, action: #selector(self.textChanged(_:)), for: .editingChanged)
        })

        let cancel = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: { (_) -> Void in

        })

        let action = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: { (_) -> Void in
            let textfield = alert.textFields!.first!

            //Do what you want with the textfield!
        })

        alert.addAction(cancel)
        alert.addAction(action)

        self.actionToEnable = action
        action.isEnabled = false
        self.present(alert, animated: true, completion: nil)
    }

    func textChanged(_ sender:UITextField) {
        self.actionToEnable?.isEnabled  = (sender.text! == "Validation")
    }
Sourabh Sharma
  • 8,222
  • 5
  • 68
  • 78
  • Really helpful. Thanks :) – Edouard Barbier Dec 13 '17 at 07:00
  • Unfortunately this doesn't work in Swift 5: There's an error for `#selector` (`Argument of '#selector' refers to instance method 'textChanged' that is not exposed to Objective-C`) but if I change the function to `@objc func textChanged`, then I get a different error (`@objc can only be used with members of classes, @objc protocols, and concrete extensions of classes`) and it wants me to remove it again. – Neph May 24 '23 at 10:58
3

I implemented a subclass of UIAlertController for conveniently adding text fields and associated enabling and disabling of buttons. The basic logic is similar to that Sourabh Sharma but everything is encapsulated in this subclass for tidiness. This should be helpful if your project involves a lot of such alert functionalities.

public class TextEnabledAlertController: UIAlertController {
  private var textFieldActions = [UITextField: ((UITextField)->Void)]()

  func addTextField(configurationHandler: ((UITextField) -> Void)? = nil, textChangeAction:((UITextField)->Void)?) {
      super.addTextField(configurationHandler: { (textField) in
        configurationHandler?(textField)

        if let textChangeAction = textChangeAction {
            self.textFieldActions[textField] = textChangeAction
            textField.addTarget(self, action: #selector(self.textFieldChanged), for: .editingChanged)

        }
    })

}

  @objc private func textFieldChanged(sender: UITextField) {
    if let textChangeAction = textFieldActions[sender] {
        textChangeAction(sender)
    }
  }
}

To use it, just provide a textChangeAction block when adding the text fields:

    alert.addTextField(configurationHandler: { (textField) in
        textField.placeholder = "Your name"
        textField.autocapitalizationType = .words
    }) { (textField) in
        saveAction.isEnabled = (textField.text?.characters.count ?? 0) > 0
    }

For the full example, see the git page.

CodeBrew
  • 6,457
  • 2
  • 43
  • 48