UIAlertController addTextFieldWithConfigurationHandler. Odd functionality - ios

I am making an alert that has a couple of text fields, this will be saved on a server once save is hit. I managed to get it to function but found something odd that I didn't understand. I'm not sure how to explain it in words so I will post the code in question.
var enteredName: UITextField!
let myAlert: UIAlertController = UIAlertController(title: "", message: nil, preferredStyle: .Alert)
let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { action -> Void in
println(self.enteredName.text)
}
myAlert.addAction(saveAction)
myAlert.addTextFieldWithConfigurationHandler { textField -> Void in
textField.placeholder = "Name"
self.enteredName = textField
}
self.presentViewController(myAlert, animated: true, completion: nil)
Now, the above code works, and I have no problems with it, however, I had a very similar block of code originally that did not work. The above code snippet will display an alert with a text field, after typing in something and hitting save it will print what you typed. The below code was supposed to do that but printed blank lines, I assume empty strings.
var enteredName: String!
let myAlert: UIAlertController = UIAlertController(title: "", message: nil, preferredStyle: .Alert)
let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { action -> Void in
println(self.enteredName)
}
myAlert.addAction(saveAction)
myAlert.addTextFieldWithConfigurationHandler { textField -> Void in
textField.placeholder = "Name"
self.enteredName = textField.text
}
self.presentViewController(myAlert, animated: true, completion: nil)
As you can see, not much has been changed, and to me, both code blocks look like they should run. The differences: In the code snippet that did not work
The variable is a String instead of a UITextField
The variable is being saved to textField.text instead of textField
The alert is printing self.enteredName instead of self.enteredName.text
So my question is this, why did the bottom text block not work properly while the top block did?

The addTextFieldConfigurationHandler method is called before the user has an opportunity to to type anything and press save (and therefore, before the code you put in the saveAction is ever executed).
The only time in the bottom example you ever assign anything to your enteredName string is when you're adding the text field configuration handler. The text field configuration handler is never called at any point after the user sees the text field (so never after the user has an opportunity to type in the text field).
And the string variable can't just magically get the value from the text field.

Related

Declaration of Handlers doesn't work

I've read a few examples on declaring handlers for UIAlertViews and somehow I just can't grasp the concept of the proper syntax. I've seen a few examples where they do one of the following:
- handler: someFunction
- handler: {action in SomeFunction} (what does "action in" mean?)
- handler: { _ in print("Foo!") (again, what does "in" mean?)
My biggest concern is that I don't know what these things mean. and I'm trying to use the first style however I get the following error: "Variable used within its own initial value"
let answerVCAlert = UIAlertController(title: "Your turn", message: "What's the answer?", preferredStyle: .alert)
let submitAnswer = UIAlertAction(title: "Submit", style: .default, handler: submitAnswer(answer: " ")) //grab from textfield
let noAnswer = UIAlertAction(title: "No Answer", style: .default, handler: submitAnswer(answer: " "))
func submitAnswer(answer: String) {
print ("The string passed is \(answer)")
//compare answer to correct answer
}
func attemptAnswer() {
answerVCAlert.addAction(submitAnswer)
answerVCAlert.addAction(noAnswer)
//answerVCAlert.addTextField //how ??? too many different examples
self.present(answerVCAlert, animated: true, completion: nil)
}
As mentioned in the comments, now's the time to learn how to using closures or completion handlers. It's time well spent.
Here's some working code (based on your's but with some minor changes) that also includes a textfield. A good Swift 3 example of getting the text from an alert textfield is this SO answer. There is a change in Swift 4 syntax in my code below.
I've indented the code in hopes it helps you understand completion handlers better. I added some inline comments also.
Here's the alert:
// I changed the name of your alert controller for brevity
let alertVC = UIAlertController(
title: "Your turn",
message: "What's the answer?",
preferredStyle: .alert)
// Add a textfield called answerText. You may not want the placeholder to be blank.
alertVC.addTextField(configurationHandler: {(answerText: UITextField!) in
answerText.placeholder = "Default answer"
})
// Add a Submit button that will call submitAnswer()
let submitAnswer = UIAlertAction(
title: "Submit",
style: .default,
handler: { action -> Void in
self.submitAnswer(alertVC.textFields![0])
})
alertVC.addAction(submitAnswer)
// Add a No Answer button that will call noAnswer()
// NOTE: I changed this to be style of cancel... check out how it's rendered.
let noAnswer = UIAlertAction(
title: "No Answer",
style: .cancel,
handler: { action -> Void in
self.noAnswer()
})
alertVC.addAction(noAnswer)
// I think you *want* everybody to try to answer, so let's make that the preferred action.
alertVC.preferredAction = submitAnswer
present(
alertVC,
animated: true,
completion: nil)
And here's the results:
func submitAnswer(_ answer: String) {
print ("The answer is \(answer)")
//compare answer to correct answer
}
func noAnswer() {
print ("Cancel was tapped.")
}
Among the changes I made are:
Changed the style of one action from .default to .cancel to give you an idea of what that does.
Added self to the completion handler calls - they are required.
Changed the signature of submitAnswer() to no longer need the parameter label. It's Swiftier that way.
Changed the noAnswer action call from submitAnswer() to noAnswer() to distinguish what the user tapped on.

How to get access to button placed on UIAlertController from handler of UITextField placed on UIAlertController?

i’m using UIAlertController for asking user enter a string, and this string should not be empty. For this purpose i'm adding handler for every editing event on UITextField located on UIAlertController. This handler checks, if the textField is empty, it makes «OK» button (located on UIAlertController) disabled, and, if the textField is not empty, it makes «OK» button enabler. «OK» button also located on UIAlertController.
I have a problem with access to the «OK» button from this
handler function:
There is no opportunity to pass the button as a parameter directly to the handler function because i’m calling it using #selector.
I’ve also tried to access UIAlertController through the UITextField.superview from handler function, but it didn’t work: it returned UIView, which can’t be downcasted to UIAlertController. And it’s not clear why it is so: UITextField is subview of UIAlertController and consequently UITextField.superview must return UIAlertController.
I’ve also tried the easiest way: declare UIAlertController as property in my ViewController class: this allows me to access the button from handler function without passing it directly to function. But in this case i’ve collided with another problem: at the runtime Xcode shows this warning in debug area: Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior ()
And my question is how to get access to «OK» button from my handler function?
class TVC_ProgramsList: UITableViewController {
var enterNameAC = UIAlertController()
#IBAction func addpush(sender: UIBarButtonItem) {
addProgram()
}
func addProgram() {
enterNameAC = UIAlertController(title: "Adding program", message: "Enter program name", preferredStyle: UIAlertControllerStyle.Alert)
enterNameAC.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil))
enterNameAC.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
enterNameAC.addTextFieldWithConfigurationHandler { (textField) -> Void in
textField.placeholder = "Program name"
textField.addTarget(self, action: #selector(TVC_ProgramsList.enterProgramNameAC_editingTextField), forControlEvents: UIControlEvents.AllEditingEvents)
}
self.presentViewController(enterNameAC, animated: true, completion: nil)
}
func enterProgramNameAC_editingTextField() {
let programNameString = enterNameAC.textFields![0].text!
if programNameString.characters.count > 0 {
enterNameAC.actions[1].enabled = true
} else {
enterNameAC.actions[1].enabled = false
}
}
}
Why don't you make an attribute of the UIAlertAction? I've changed your code:
class TVC_ProgramsList: UITableViewController {
var enterNameAC = UIAlertController()
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil)
#IBAction func addpush(sender: UIBarButtonItem) {
addProgram()
}
func addProgram() {
enterNameAC = UIAlertController(title: "Adding program", message: "Enter program name", preferredStyle: UIAlertControllerStyle.Alert)
enterNameAC.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil))
enterNameAC.addAction(self.okAction)
enterNameAC.addTextFieldWithConfigurationHandler { (textField) -> Void in
textField.placeholder = "Program name"
textField.addTarget(self, action: #selector(TVC_ProgramsList.enterProgramNameAC_editingTextField), forControlEvents: UIControlEvents.AllEditingEvents)
}
self.presentViewController(enterNameAC, animated: true, completion: nil)
}
func enterProgramNameAC_editingTextField() {
let programNameString = enterNameAC.textFields![0].text!
// use okAction here
self.okAction.enabled = programNameString.characters.count > 0
}
}
Passing it with addTarget won't be possible. But it's a valid approach to make a class attribute out of it and therefore making it accessible in other parts of your ViewController.
Also i found another solution of this problem: it's enough to make lazy declaration of enterNameAC to avoid alert message in debug area at runtime:
lazy var enterNameAC = UIAlertController()
But it's not all: All i said it was about Swift 2, but yesterday i updated to Swift 3, and, it's miracle: it works without any debug area messages, even without lazy declaration!

UIAlertAction accessing old class variable

I have programmatically added UITextView's and a class object that gets some part of it changed upon calling:
textViewShouldEndEditing(textView: UITextView)
I handle saving the object fields for the user as a batch save, but the problem is that the last edited UITextView value doesn't ever make it to the save, even though the textViewShouldEndEditing function gets called. The only other complication is that the batch saving is part of a confirmation message via UIAlertAction.
To clarify, the class object that the UIAlertAction accesses appears to be out of date even though all functions are called in the correct order. I hope this is clear.
var tmpGuest:VIG = VIG()
func textViewShouldEndEditing(textView: UITextView) -> Bool {
let saveTxt:String = (textView.text == "--" ? "" : textView.text)
//SET SOME tmpGuest VALUES HERE
return true
}
#IBAction func editInfo(sender: AnyObject) {
let actionSheetController: UIAlertController = UIAlertController(title: "Are you sure you wish to save?", message: "Changes are permanent.", preferredStyle: .Alert)
let okAction: UIAlertAction = UIAlertAction(title: "Yes", style: .Default) { action -> Void in
//SAVE
self.setGuest.setRecords(guestMainTmp: self.tmpGuest)
}
actionSheetController.addAction(okAction)
self.presentViewController(actionSheetController, animated: true, completion: nil)
}

Swift - Force a user to enter input (i.e. not blank input) in a UIAlertController

I want to query a user for his or her name in one of my apps. I'd like to have a check to make sure that the user entered a non-blank string and then re-prompt the user with an error message if the input is invalid. So far this is what I have:
while name == "" {
name = promptUserForName()
}
And the promptUserForName( ) method is this:
private func promptUserForName() -> String {
let alert = UIAlertController(title: "Enter your name:", message: nil, preferredStyle: .Alert)
// Presents a keyboard for user to enter name
var userInputField: UITextField?
alert.addTextFieldWithConfigurationHandler { (textField: UITextField!) in
userInputField = textField
}
var name: String = ""
let defaultAction = UIAlertAction(title: "OK", style: .Default, handler: {(action) -> Void in
if let userInfo = userInputField!.text {
name = userInfo
}
})
alert.addAction(defaultAction)
self.presentViewController(alert, animated: true, completion: nil)
return name
}
Unfortunately, the while loop just loops infinitely without prompting the user and waiting for a response. Is there a way to make this work, perhaps by putting the while loop within the promptUserForName( ) method? Thanks.
Don't use a while-loop. Just check whether the name is blank in the alert's completion handler. If it is blank, call promptUserForName again; otherwise do whatever you need to do. You probably shouldn't return anything from this method, because it's asynchronous; instead, pass the name into another method from within the alert's completion handler.

Unwanted top space in UIAlertController TextField

I am trying to create an dialog box to ask a file name to save.
Somehow the showed textfield has an extra space above and I cannot remove it. The space is not usable (it is not a line, just a top margin)
I couldn't get rid of it even by trying to change the .frame.height property of the textfield.
Please help! I don't understand why this is happening?
The ScreenShot: http://i.stack.imgur.com/dQYjz.png
The code is:
var fieldSetListName: UITextField!
func askForSetListName() {
//Create the AlertController
let alertController: UIAlertController = UIAlertController(title: "Name", message: "Enter a name for Setlist", preferredStyle: .Alert)
//Create and add the Cancel action
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//Do some stuff
}
alertController.addAction(cancelAction)
//Create and an option action
let okAction: UIAlertAction = UIAlertAction(title: "OK", style: .Default) { action -> Void in
//Do some other stuff
self.saveSetListAndDismiss(self.fieldSetListName.text)
}
alertController.addAction(okAction)
//Add a text field
alertController.addTextFieldWithConfigurationHandler { textField -> Void in
textField.textColor = UIPalette.DarkText
textField.placeholder = "(venue, festival, date etc.)"
self.fieldSetListName = textField
}
//Present the AlertController
self.presentViewController(alertController, animated: true, completion: nil)
}
Add textField like this:
var example :UITextField
var inputText : String
let exampletextField = actionSheetController.textFields![0] as UITextField
exampletextField.textColor = UIPalette.DarkText
exampletextField.placeholder = "(venue, festival, date etc.)"
inputText = exampletextField.text
Found the solution.
For anybody who comes across this kind of problem, setting rowHeight property of UITableView globally using UIAppearance effects the textfield height inside the alertview:
I got rid of the line: UITableView.appearance().rowHeight = 50

Resources