49

I've got a UIAlertController which is prompted to the user when they choose "Enter Password" on the TouchID screen. How do I recover the user's input after presenting this on screen?

let passwordPrompt = UIAlertController(title: "Enter Password", message: "You have selected to enter your passwod.", preferredStyle: UIAlertControllerStyle.Alert)
passwordPrompt.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default, handler: nil))
passwordPrompt.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))

passwordPrompt.addTextFieldWithConfigurationHandler({(textField: UITextField!) in
                        textField.placeholder = "Password"
                        textField.secureTextEntry = true
                        })

presentViewController(passwordPrompt, animated: true, completion: nil)

I know the OK button should probably have a handler, but right now this code doesn't really do anything, but I want to display the output of the text field through a println. This is really just a test app for me to learn Swift and some new API stuff.

Andrew
  • 968
  • 2
  • 11
  • 22
  • You want to set the `delegate` of the controller and then implement the delegate callbacks – Jack Jun 11 '14 at 21:15
  • 1
    I thought UIAlertController didn't have a delegate. It doesn't appear to be mentioned in the docs. – Andrew Jun 11 '14 at 21:43
  • Oh no my mistake, I confused it with `UIAlertView`. You should pass in a function to the `handler` for each button. The handler will be called when its pressed – Jack Jun 11 '14 at 21:46
  • 1
    How does the handler get access to the UITextField? The closure has a return of Void and the UITextField's scope is within the closure. The handler would represent the action of clicking OK or Cancel, I don't see how that would let me query the inputted text though. – Andrew Jun 11 '14 at 21:56
  • 5
    The handler's signature is :`handler: ((UIAlertAction!) -> Void)!)`, so the sender does get passed in. You will have to access `passwordPrompt.textFields` to get the inputted text inside that handler. – Jack Jun 11 '14 at 22:10
  • Amazing. That did it. I'm confused on reading the method signature though, how did you know it passed the sender? this swift stuff is still a tad confusing (hence why I'm trying to write this code) – Andrew Jun 11 '14 at 22:24

4 Answers4

66

I've written up a blog post exploring the new API. You can just capture a local variable in the closure and you're good to go.

var inputTextField: UITextField?
let passwordPrompt = UIAlertController(title: "Enter Password", message: "You have selected to enter your password.", preferredStyle: UIAlertControllerStyle.Alert)
passwordPrompt.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default, handler: nil))
passwordPrompt.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: { (action) -> Void in
    // Now do whatever you want with inputTextField (remember to unwrap the optional)
}))
passwordPrompt.addTextFieldWithConfigurationHandler({(textField: UITextField!) in
    textField.placeholder = "Password"
    textField.secureTextEntry = true
    inputTextField = textField
 })

presentViewController(passwordPrompt, animated: true, completion: nil)

That way you avoid having an unnecessary property on your view controller.

Axe
  • 6,285
  • 3
  • 31
  • 38
Ash Furrow
  • 12,391
  • 3
  • 57
  • 92
  • I ported this to Swift 3. http://stackoverflow.com/a/39822616/211457 If someone wants to erase my answer and add my port to this answer that'd be ok with me! – Andy Oct 02 '16 at 23:05
48

I know comments have been posted to answer the question, but an answer should make the solution explicit.

@IBOutlet var newWordField: UITextField
func wordEntered(alert: UIAlertAction!){
    // store the new word
    self.textView2.text = deletedString + " " + self.newWordField.text
}
func addTextField(textField: UITextField!){
    // add the text field and make the result global
    textField.placeholder = "Definition"
    self.newWordField = textField
}

// display an alert
let newWordPrompt = UIAlertController(title: "Enter definition", message: "Trainging the machine!", preferredStyle: UIAlertControllerStyle.Alert)
newWordPrompt.addTextFieldWithConfigurationHandler(addTextField)
newWordPrompt.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default, handler: nil))
newWordPrompt.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: wordEntered))
presentViewController(newWordPrompt, animated: true, completion: nil)

This displays a prompt with a text field and two action buttons. It reads the text field when done.

pkamb
  • 33,281
  • 23
  • 160
  • 191
Scott
  • 2,568
  • 1
  • 27
  • 39
  • 1
    you can also go with a weak reference to `newWordField` `weak var newWordField: UITextField` that way you do not own the textfield as it must already be pointed strongly by the alert controller ` – geekay Apr 09 '17 at 11:56
8

Do this inside your closure:

let tf = alert.textFields[0] as UITextField

Here is a gist

Leonid Beschastny
  • 50,364
  • 10
  • 118
  • 122
Gene De Lisa
  • 3,628
  • 1
  • 21
  • 36
  • 1
    If you use this approach, make sure to capture `alert` as `unowned` to avoid a reference cycle, which causes a memory leak. – Ash Furrow Sep 07 '14 at 18:58
  • @AshFurrow Can you please elaborate? How do you capture `alert` as `unowned`? – Isuru Sep 17 '14 at 12:20
  • Actually, if you look at Apple's UICatalog example (Swift/UICatalog/AlertControllerViewController.swift), they add a NSNotificationCenter observer for handleTextFieldTextDidChangeNotification when you configure the textfield. – Gene De Lisa Sep 17 '14 at 14:41
  • 1
    Sure, that works, too. My point was only that if you access the `alert.textFields` array from within the action handler closure, you'll have a reference cycle. The documentation you need is titled "[Resolving Strong Reference Cycles for Closures](https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html)". – Ash Furrow Sep 17 '14 at 15:10
  • 1
    textFields is optional, at least in Xcode 6.2. So it should be `let tf = alert.textFields![0] as UITextField`. – Valeriy Van Mar 13 '15 at 12:53
8

I ported Ash Furrow's answer to Swift 3.

    var inputTextField: UITextField?
    let passwordPrompt = UIAlertController(title: "Enter Password", message: "You have selected to enter your password.", preferredStyle: UIAlertControllerStyle.alert)
    passwordPrompt.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.default, handler: nil))
    passwordPrompt.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: { (action) -> Void in
        // Now do whatever you want with inputTextField (remember to unwrap the optional)
    }))
    passwordPrompt.addTextField(configurationHandler: {(textField: UITextField!) in
        textField.placeholder = "Password"
        textField.isSecureTextEntry = true
        inputTextField = textField
    })

    present(passwordPrompt, animated: true, completion: nil)
Community
  • 1
  • 1
Andy
  • 1,815
  • 2
  • 22
  • 49