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.
Related
I have an app that allows users to save their profile. In order for them to be able to sign up, I want to check and see if they have agreed to the apps terms and conditions. The issue I am having is if the user doesn't agree to them, they will see an alertController telling them to agree. However, the app still continues to execute the remainder of the code.
func checkIfChecked() {
if self.checkbox.imageView.isHidden == true {
let alert = UIAlertController(title: "Hold up!",message:" You must agree to our Community Guidelines before you can sign up.", preferredStyle: UIAlertController.Style.alert)
let continueButton = UIAlertAction(title: "Got it!", style: .default, handler: {(_ action: UIAlertAction) -> Void in
})
continueButton.setValue(GREEN_Theme, forKey: "titleTextColor")
alert.addAction(continueButton)
self.present(alert, animated: true, completion: nil)
}
if self.checkbox2.imageView.isHidden == true {
let alert = UIAlertController(title: "Hold up!",message:" You must agree to our Terms & Conditions before you can sign up.", preferredStyle: UIAlertController.Style.alert)
let continueButton = UIAlertAction(title: "Got it!", style: .default, handler: {(_ action: UIAlertAction) -> Void in
})
continueButton.setValue(GREEN_Theme, forKey: "titleTextColor")
alert.addAction(continueButton)
self.present(alert, animated: true, completion: nil)
}
}
#objc func handleRegister() {
checkIfChecked()
let hud = JGProgressHUD(style: .dark)
hud.textLabel.text = "Registering!"
hud.show(in: view)
guard let email = emailTextField.text, let password = passwordTextField.text, let name = nameTextField.text, let phonenumber = phonenumberTextField.text else {
print("Error")
return
the remainder of code....
}
}
if the checkBoxs are checked, there is no issue. But if they are not checked, then the users information will still be saved to the data base without them logging in. So I am trying to stop the execution of handleRegister after checkIfChecked is called only if the boxs were not checked.
Not sure if this is the safest way to fix the issue I am having, but what I did to fix the problem is inside of of the handleRegister, I added
checkIfChecked()
this has to be after the checkIfChecked that way the alertControllers can show.
if self.checkbox.imageView.isHidden == true {
return
} else if self.checkbox2.imageView.isHidden == true {
return
}
it does stop the execution of code if these lines are true.
I'm extremely new to iOS. I'm trying to show a dialog to the user to get some input, but the actions are never triggered. I've been searching on the net for hours and no answer seem to work for me.
Here's the function I'm trying to use to show the dialog:
private func showAmountDialog(type: String, onComplete: #escaping (Double) -> Void) {
let alert = UIAlertController(title: "Enter an amount", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: LzStrings.Common_Cancel, style: .cancel, handler: nil))
alert.addTextField(configurationHandler: { textField in
textField.placeholder = "0.00 \(type)"
textField.keyboardType = .decimalPad
})
alert.addAction(UIAlertAction(title: LzStrings.Common_OK, style: .default) { (UIAlertAction) in
if let input = alert.textFields?.first?.text, let amount = Double(input) {
print("Your amount: \(amount)")
}
})
self.present(alert, animated: true)
}
self here is my ViewController which has a parent of UIViewController type and several other protocols.
What I might be doing wrong?
EDIT: The way I knew it isn't executing is using break-points and not by relying on print("...")
Also, since I added the TextField right before adding the action, the nullability check is useless and the textFields.first is never nil, so in both cases, a break-point should be triggered or the print("...") should be executed, which neither of them is happening.
EDIT 2: Since the if statement can do a little distraction, I edited my code this way and tested again:
alert.addAction(UIAlertAction(title: LzStrings.Common_OK, style: .default) { (UIAlertAction) in
if let input = alert.textFields?.first {
if let amount = Double(input.text ?? "") {
print("Your amount: \(amount)")
} else {
print("Can't cast this string to double")
}
} else {
print("Text field is null")
}
})
Still, no feedback from the dialog.
PS: Even the Cancel button doesn't work.
EDIT 3: My dismiss function is overridden in the super class, but it passes completion closure normally:
override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
if let navigationController = self.navigationController as? NavigationController {
navigationController.dismiss(animated: flag, completion: completion)
} else {
super.dismiss(animated: flag, completion: completion)
}
}
After having a conversation with one of my colleagues, we found out that to show standard UIAlertController we must use this:
self.view.window!.rootViewController?.present(alert, animated: true, completion: nil)
Instead of this
self.present(alert, animated: true, completion: nil)
It fixed my issue. I hope someone will find this helpful.
Another option is to use an extention for ViewController:
extension UIViewController {
//Show a basic alert
func showAlert(alertText : String, alertMessage : String) {
let alert = UIAlertController(title: alertText, message: alertMessage, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Got it", style: UIAlertActionStyle.default, handler: nil))
//Add more actions as you see fit
self.present(alert, animated: true, completion: nil)
}
}
The following function below is displaying an alert if no selections are made in UITableView.
But I do not want the alert to come up when the TableView has no records. How can this be done?
Work so far:
#IBAction func onTapNextButton(_ sender: UIBarButtonItem) {
guard let selectedIndexPaths = tableView.indexPathsForSelectedRows, !selectedIndexPaths.isEmpty else {
//Show Alert here...
let alert = UIAlertController(title: "Alert..!!", message: "You must select atleast 1 row before proceeding.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
return
}
}
First check if the data source array is not empty, replace dataSourceArray with the real name
guard !dataSourceArray.isEmpty else { return }
guard let selectedIndexPaths = tableView.indexPathsForSelectedRows,
!selectedIndexPaths.isEmpty else {
let alert ...
Whatever the object is that you're using as your data source (I'm assuming it's an array), check to see if that object (array) has zero elements. So, for example, something like:
//tableViewData should be the name of the array you're using as your data source
if tableViewData.isEmpty {
return
}
That way if there are no rows, you exit the function, and it won't display the alert.
// this is one way of checking if the tableView is empty
#IBAction func onTapNextButton(_ sender: UIBarButtonItem) {
if tableView.visibleCells.isEmpty {
//table view is empty: do something
} else {
guard let selectedIndexPaths = tableView.indexPathsForSelectedRows, !selectedIndexPaths.isEmpty else {
//Show Alert here...
let alert = UIAlertController(title: "Alert..!!", message: "You must select atleast 1 row before proceeding.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
return
}
}
}
***** Hope you like it *****
I have a screen with a collection view, and a plus sign bar button item. When the plus sign is pressed, an alert window pops up, where the user can add information to the list. Upon hitting OK, I am trying to refresh the collection view, but I'm doing something wrong.
The print statement "passed guard" is achieved, and I can get the information they entered. Just can't refresh the view to reflect this without leaving and coming back. Any guidance? I've run into this a few times actually, so I'm clearly missing something. Thanks very much in advance.
#objc func newButtonPressed() {
let alert = UIAlertController(title: "Add", message: "", preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = "Name"
}
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action) in
var name = ""
guard let textFields = alert.textFields else { return }
guard let navController = self.parent as? UINavigationController else { return }
guard let settingsVC = navController.topViewController as? SettingsVC else { return }
print("passed guard") // success
DispatchQueue.main.async {
settingsVC.collectionView.reloadData()
settingsVC.view.backgroundColor = .red
// For testing purposes, explicitly using main thread and setting to red
}
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
May be you need to alter the collection dataSource
guard let textFields = alert.textFields else { return }
settingsVC.arr.append(textFields.first!.text!) // arr is collection dataSource
settingsVC.collectionView.reloadData()
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.