Trouble returning a string from Alert - ios

I am building a todo-list app and I am having a lot of trouble returning the text input from an Alert.
This is in a separate file ex: 'AddItem.swift'
func showAddItemDialog(view: UIViewController) -> String {
var textOfTask = UITextField()
var textValue = ""
let diag = UIAlertController(title: "Add Task", message: "Enter a task name", preferredStyle: .Alert)
diag.addTextFieldWithConfigurationHandler({ (textField) -> Void in })
diag.addAction(UIAlertAction(title: "Add", style: .Default, handler: { (action) -> Void in
textOfTask = diag.textFields![0] as UITextField
textValue = textOfTask.text!
addListItem(textValue)
}))
diag.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: { (action: UIAlertAction!) in }))
view.presentViewController(diag, animated: true, completion: nil)
print("returning " + textValue)
return textValue
}
And I am trying to have the text value ('textValue') of the Alert's text box be returned to the caller.
I have tried a bunch of ways but have not come up with anything and what I have above returns nothing because the function does not stop and wait for the alert to show before returning. I want to avoid putting this code into the ViewController file as I have read that it's bad practice, but I really can't figure this out.
If anyone has any ideas, please let me know! Thanks!
Edit:
I am calling this function with:
#IBAction func didPressAdd(sender: AnyObject) {
showAddItemDialog(self)
}

You are correct that the code won't wait for the person to enter data and click the OK button. The answer is to use a completion handler.
// This is a slightly modified version of your code
func showAddItemDialog(view: UIViewController, completion: (text: String?) -> Void ) {
var textOfTask = UITextField()
var textValue = ""
let diag = UIAlertController(title: "Add Task", message: "Enter a task name", preferredStyle: .Alert)
diag.addTextFieldWithConfigurationHandler({ (textField) -> Void in })
diag.addAction(UIAlertAction(title: "Add", style: .Default, handler: { (action) -> Void in
textOfTask = diag.textFields![0] as UITextField
textValue = textOfTask.text!
addListItem(textValue)
completion(text: textValue)
}))
diag.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: { (action: UIAlertAction!) in
completion(text: nil)
}))
view.presentViewController(diag, animated: true, completion: nil)
}
// In the view controller
#IBAction func didPressAdd(sender: AnyObject) {
// Call it like this:
showAddItemDialog(self) {
(text) in
// handle the result value here
if let textUserEntered = text {
// User entered some text and pressed OK
}
else {
// User pressed cancel
}
}
}

As #nhgrif says in his comment, you can't return a value as the result of your function. The function returns as soon as it hands the alert to the system for display, and before the alert is even drawn to the screen.
This is a very common beginner mistake when dealing with async methods.
You need to refactor your showAddItemDialog function to take a completion closure with a string parameter. In the closure for your add action, fetch the text from the field of the alert and then invoke the closure that's passed to you, giving it the string.
Then when you call your showAddItemDialog function, pass it a closure that does whatever you need to do with the text you collect from the user.

Related

How to edit alertViewController without dismissing it accordingly to the action

I have an alert in my app where I put a textfield. The user can use it to add some values in an array. However I want all the values to be different. So if a user inserts an existing value, I want the textfield to be cleared and present a different placeholder text telling the user to insert a new value.
This is what I'm doing now:
func appendWord(){
let alertController = UIAlertController(title:"insert a word", message: nil, preferredStyle: UIAlertController.Style.alert)
alertController.addTextField { (textField : UITextField) -> Void in
textField.placeholder = "insert here"
textField.delegate = self
}
let cancelAction = UIAlertAction(title: "cancel", style: UIAlertAction.Style.cancel) { (result : UIAlertAction) -> Void in
}
let okAction = UIAlertAction(title: "save", style: UIAlertAction.Style.default) { (result : UIAlertAction) -> Void in
let newName = alertController.textFields![0].text! as String
//Useless Stuff to Append items here [...]
//If the item already exists then i call the following function which is inside of an if statement...
self.errorInCreation()
}
alertController.addAction(cancelAction)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
func errorInCreation(){
let alertController = UIAlertController(title:"Insert a new word", message: nil, preferredStyle: UIAlertController.Style.alert)
alertController.addTextField { (textField : UITextField) -> Void in
textField.placeholder = "The word already exists. Insert a new one"
textField.attributedPlaceholder = NSAttributedString(string: "The word already exists. Insert a new one",attributes: [NSAttributedString.Key.foregroundColor: UIColor.red])
textField.delegate = self
}
let cancelAction = UIAlertAction(title: "cancel", style: UIAlertAction.Style.cancel) { (result : UIAlertAction) -> Void in
}
let okAction = UIAlertAction(title: "save", style: UIAlertAction.Style.default) { (result : UIAlertAction) -> Void in
let newName = alertController.textFields![0].text! as String
//Useless Stuff to Append items here [...]
//If the item already exists then i call the following function which is inside of an if statement...
self.errorInCreation()
}
alertController.addAction(cancelAction)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
This should present a new alertViewController until the user inserts a new word. However this doesn't happen. When I press the save button, the alert closes.
I tried to edit the current alert but it's not really possible.
How could I clear the inserted text, change the placeholder name and let the user insert a new word?
I found this person who has my same problem but the solution pointed out here didn't work.
Presenting new AlertViewController after dismissing the previous AlertViewController - Swift
The solution is actually quite simple: don't use UIAlertController. It's just a specialized presented view controller, and you don't get much control over how it looks or behaves; in particular, it dismisses when a button is tapped, which is not what you want. So just use a custom presented view controller where you have the kind of control you're after.

Passing the function of the UIAlertAction to the UIAlertController extension

I want to have a base UIAlertController and I want to use it in different classes by just passing the buttons and their closures. To achieve this, I created an extension from UIAlertController like below:
extension UIAlertController {
func showAlert(buttons: [ButtonsAction]?) -> UIAlertController {
let alert = self
guard let alertButtons = buttons else {
return alert
}
for button in alertButtons {
let alertAction = UIAlertAction(title: button.title, style: button.style, handler: {action in
button.handler()
})
alert.addAction(alertAction)
}
return alert
}
}
for my buttons I have a struct:
struct ButtonsAction {
let title: String!
let style: UIAlertAction.Style
let handler: () -> Void
}
In one of my viewControllers I have a function which shows the alert. In that function I have a title and a message then I want to have 1 button to dismiss the alert. The function is something like this:
func fetchFaild(title: String, message: String) {
let buttons = ButtonsAction.init(title: "cancel", style: .cancel, handler: {action in
//here I want to dissmiss the alert I dont know how
})
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert).showAlert(buttons: buttons)
alert.show(self, sender: nil)
}
I have problem adding buttons to the Alert and I don't know how to add actions to the buttons.
I know this is not the best practice here. If any one knows any example or any tutorial that can help me achieve this I really appreciate it.
An extension of UIViewController might be a more reasonable solution and the ButtonsAction struct seems to be redundant.
extension UIViewController {
func showAlert(title: String, message: String, actions: [UIAlertAction], completion: (() -> Void)? = nil) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
actions.forEach{alertController.addAction($0)}
self.present(alertController, animated: true, completion: completion)
}
}
class MyController : UIViewController {
func fetchFailed(title: String, message: String) {
let actions = [UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) in
print("Cancel tapped")
})]
showAlert(title: title, message: message, actions: actions)
}
}

How to implement make a handler in swift 4 to enable code to execute in the order needed

I have written a function that brings up an alert that requires the user to make a menu choice. That choice will be used to change a button name. The problem is the button name is changed before the user choice is made. It is changed to the previous function result.
I have read that a handler can be used to delay execution of the result, but I can't figure out how to use it.
#IBAction func selectProp(_ sender: Any) {
propName.setTitle(menulist(title:"propName", message:""), for: .normal)
print("selected property = ", choice) // }
}
func menulist (title: String, message: String) -> String {
let title1 = "Select Property"
let alert = UIAlertController(title: title1, message: message, preferredStyle:UIAlertControllerStyle.alert)
let k = rentals.count
if k > 0 {
for i in 0 ... k-1 {
alert.addAction(UIAlertAction(title:rentals[i], style: .default, handler: {action in
choice = rentals[i]
print("choice=",choice)
}))
}
alert.addAction(UIAlertAction(title: "Cancel", style: .destructive, handler: {action in
choice = "Select"
print("choice=",choice)
}))
self.present(alert, animated: true, completion: nil)
}
return choice
}
The problem is the button name is changed before the user choice is made and the print statement is executed before the user makes the choice. The results, button change and print, are based on the previous user input choice.
I have read that a handler can be used to delay execution of the result, but can't figure out how to use it.
Exactly, such handlers are called closures. Since UIAlertAction uses closure to deliver the result, you need to use closure in your function instead of returning value.
func showMenuList(title: String, message: String, completion: #escaping (_ rental: String?) -> ()) {
let title = "Select Property"
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
// loop through all rentals and add action to alert for each of them
rentals.forEach { (rental) in
let rentalAction = UIAlertAction(title: rental, style: .default, handler: { _ in
completion(rental)
})
alert.addAction(rentalAction)
}
// add cancel action
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
completion(nil)
})
alert.addAction(cancelAction)
present(alert, animated: true, completion: nil)
}
Then you can use this function like this:
showMenuList(title: "propName", message: "") { result in
guard let result = result else {
// handle Cancel action
return
}
// handle property selected
print(result)
}
I think I see what's happening here. The #IBAction func selectProp() is being called due to an action (.touchUpInside perhaps?), and it is setting the propName before the user has selected their choice using the alert.
What you need to do here is move the propName.setTitle(...) into the handler itself, so it is called at the point in time that the user selects the option via the alert:
alert.addAction(UIAlertAction(title:rentals[i], style: .default, handler: {action in
propName.setTitle(rentals[i], for: .normal)
....
At the moment, the propName title is being set when the user triggers selectProp, not when the user selects their choice via the alert.
Hope that helps!
Let me know if you have any more questions.

Alert doesn't pop up when expected

I want to change a label's text.
#IBAction func renameLabel(_ sender: UIButton) {
let labelTextToBeChanged = "some text"
let changedLabelText = changeLabeltext(text: labelTextToBeChanged!)
// Do something
print("changedLabelText: \(changedLabelText)")
}
The function changeLabeltext() contains an alert controller, as shown below.
I expect that after the call of changeLabeltext(text: labelTextToBeChanged!) an alert window pops up and that after modifying the text the new text ist assigned to changedLabelText and is printed out.
However, after the function call an empty text ist printed out and then, after exit the IBAction function, the alert window pops up. What I am doing wrong?
func changeLabeltext(text: String) -> String{
var inputTextField:UITextField?
// Create the controller
let alertController = UIAlertController(
title: "Ă„ndere Projekt- oder Versionsname",
message: "",
preferredStyle: .alert)
// Create a textfield for input
alertController.addTextField{
(textField: UITextField!) -> Void in
textField.placeholder = text
inputTextField = textField
}
// Create the actions
let saveAction = UIAlertAction(
title: "Speichern",
style: .default,
handler: { (action: UIAlertAction!) -> Void in
inputTextField = alertController.textFields![0]
inputTextField?.text = text
})
let cancelAction = UIAlertAction(
title: "Abbruch",
style: .default,
handler: { (action: UIAlertAction!) -> Void in
})
// Add the actions to the UIAlertController
alertController.addAction(saveAction)
alertController.addAction(cancelAction)
present(alertController, animated: true, completion: nil)
return (inputTextField?.text)!
}
inputTextField is empty when this line is executed return (inputTextField?.text)!. All you have to do is change your saveAction, and from that action you coud use the text:
let saveAction = UIAlertAction(
title: "Speichern",
style: .default,
handler: { (action: UIAlertAction!) -> Void in
inputTextField = alertController.textFields![0]
inputTextField?.text = text
use(newText: text) //Or do whatever you want with the text
})
and declare the function that uses that text:
func use(NewText: String) {
//Do whatever with the new text
}
And there is no need to return a string from changeLabeltext:
func changeLabeltext(text: String) {
//...
}

How Swift code is excecuted in Xcode

I've got a Problem. I'm new to iOS programming, and i'm struggling to understand how Swift code is excecuted. For example, in the piece of code below, I would think that every line is executed right after the one above. But when it reaches the passData() function, it does not ejecute that fuction. it keeps going, and some time later (or erlier) it excecutes it (the passData function).
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if numLaunches == 0{
nombre = usuario()
numLaunches += 1
}
}
func usuario() -> String {
var tField: UITextField!
func configurationTextField(textField: UITextField!)
{
print("generating the TextField")
textField.placeholder = "Enter an item"
textField.textAlignment = .center
tField = textField
}
func handleCancel(alertView: UIAlertAction!)
{
print("Cancelled !!")
}
let alert = UIAlertController(title: "Please Enter Your Name", message: "", preferredStyle: .alert)
alert.addTextField(configurationHandler: configurationTextField)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler:handleCancel))
alert.addAction(UIAlertAction(title: "Done", style: .default, handler:{ (UIAlertAction) in
print("Done !!")
print("Item : \(String(describing: tField.text))")
self.nombre = tField.text!
}))
self.present(alert, animated: true, completion: {
print("completion block")
})
print(self.nombre)
passData()
if tField.text == nil {
return "No value"
}else{
return (tField.text!)
}
}
func passData() {
let myVC = storyboard?.instantiateViewController(withIdentifier: "SecondVC") as! AboutViewController
if myVC.playerName != nil {
myVC.playerName.text = nombre
}else{
}
navigationController?.pushViewController(myVC, animated: true)
print("pasa el dato")
}
So, the Problem is, that i need to pass the content of the variable "nombre" to another VC. But when the passData function is excecuted that variable is empty. I thought if i called that function after the variable was updated, it Will pass the right content. But im clearly mistaken.
I would appreciate the help!
In general, unless your functions are asynchronous, you are correct in your understanding that code is executed from top down. In this case I think you are just confused about your UIAlertAction code. It appears you are popping an alert to the user, asking them to write their name, and when they hit "Done" you want to call your passData() function. In that case, you should put that passData() call inside your UIAlertAction, like so:
alert.addAction(UIAlertAction(title: "Done", style: .default, handler:{ (UIAlertAction) in
print("Done !!")
print("Item : \(String(describing: tField.text))")
self.nombre = tField.text!
self.passData()
}))
The code inside the handler for your UIAlertActions will not be executed until the user presses that button, which is why you are finding that passData is getting called too early.

Resources