Dismissal of UIAlertController inside async function - ios

i am struggeling with a issue as following:
i start some network processing inside a async function:
func upload(
object: object,
context: NSManagedObjectContext
) async -> Dictionary<String,String> {
}
therefor to inform the user that a time consuming process has just been started i display a alert controller:
let alert = UIAlertController(
title: "Loading",
message: "Please wait...",
preferredStyle: .alert
)
self.present(alert, animated: true, completion: nil)
at the end of that function if everything worked out as planed i do dismiss that alert controller successfully, and show another alert:
alert.dismiss(
animated: true,
completion: {
let messageAlert = UIAlertController(
title: "Success",
message: "Upload complete",
preferredStyle: .alert
)
messageAlert.addAction(
UIAlertAction(
title: "OK",
style: .default,
handler: { (action: UIAlertAction) in
//
}
)
)
self.present(
messageAlert, animated: true, completion: nil
)
}
)
but i also want to dismiss that alert and show a new one when an error occured, but the first alert controller never gets dismissed:
guard dataString.replacingOccurrences(
of: "\n",
with: ""
) != "no valid userID" else {
error = true
alert.dismiss(
animated: true,
completion: {
let messageAlert = UIAlertController(
title: "Warning",
message: "no valid userID received",
preferredStyle: .alert
)
messageAlert.addAction(
UIAlertAction(
title: "OK",
style: .default,
handler: { (action: UIAlertAction) in
//
}
)
)
self.present(
messageAlert,
animated: true,
completion: nil
)
}
)
returnDic = [
"result": "error",
"info": "no valid userID received"
]
return returnDic
}
i just can't wrap my head around why i am able to close it at the end of that function but not somewhere down the road..
Xcode is also complaining about not being able to present a new alert if one is actually being displayed right now..
[Presentation] Attempt to present <UIAlertController: 0x15880a200> on
<Project.ContainerViewController: 0x153708290> (
from <Project.LockerViewController: 0x155018200>)
while a presentation is in progress.
i already tried to work with an observer like:
var error = Bool() {
didSet {
alert.dismiss(animated: true)
}
}
it gets called, but the view does not get dismissed again :/
any ideas?

i've found a solution:
The problem was that the presenting of that alert controller has not finished yet, and so it could not be dismissed at that time.
a fix for this is f.e.:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
alert.dismiss(animated: true)
}
i've realized this with attaching a closure block to
self.present(alert,animated: true) { print("alert controller is visible") }
and at the time of dimissal that text has not been printed out yet ;)
i hope this is usefull for somebody else as well ;)

Related

How to Dismiss Presenting View Controller when "Ok" Action Tapped in UIAlertController

I'm trying to dismiss the current view controller in the completion handler of a UIAlertAction, but it is not dismissing. I have written the following code (The loading indicator is simply a loading alert controller that I dismiss when the data was successfully uploaded):
loadingIndicator.dismiss(animated: true) {
let success = UIAlertController(title: "Successfully Uploaded", message: "", preferredStyle: .alert)
let ok = UIAlertAction(title: "Ok", style: .default, handler: { _ in
print("Ok selected") //this is working correctly
self.dismiss(animated: true, completion: nil) //this is not
})
success.addAction(ok)
self.present(success, animated: true, completion: nil)
}
However, after clicking on "Ok" in the alert, "Ok selected" is printed but the view controller is not dismissed. Nothing else shows up in the debugger.
Try dismissing it on Main thread and also check if ViewController is presented or pushed in navigation hierarchy.
loadingIndicator.dismiss(animated: true) {
let success = UIAlertController(title: "Successfully Uploaded", message: "", preferredStyle: .alert)
let ok = UIAlertAction(title: "Ok", style: .default, handler: { _ in
DispatchQueue.main.async {
self.dismiss(animated: true, completion: nil)
// If pushed use PopViewController on navigation
// self.navigationController?.popViewController(animated: true)
}
})
success.addAction(ok)
self.present(success, animated: true, completion: nil)
}
To check if ViewController is being presented or not use self.isBeingPresented property.

iOS - UIAlertController actions not triggering handler

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)
}
}

UIAlertAction closure is not being called when presenting Controller is modal

I have a UIViewController which is embedeed in UINavigationController which is being presented modally over my application's keyWindow.rootViewController. When I present UIAlertController in any screen of the presented navigation controller, the alert controller is correctly displayed but the closures for any of the UIAlertAction are not being called after being pressed.
I am displaying the same alert controller with the same code in view controllers belonging to the main navigation controller of my app and the closures are called properly.
The code for presenting the alert is very simple, here's the snippet:
// Creating Alert
func createAlert() -> UIAlertController {
let actions: [UIAlertAction] = [
UIAlertAction(title: "Something", style: .default, handler: { _ in
self.setOriginalQueue(with: items, description: description, identifier: identifier)
self.setQueue()
}),
UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
normal()
})
]
let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet, actions: actions)
return alert
}
// In ViewController
DispatchQueue.main.async {
self.present(alert, animated: true, completion: nil)
}
So the question is, why are the closures not being called?
UIAlertController does not contain four types argument. See the document.
Once you fixed that, the rest will be automatically fixed like below.
For everyone's convenience I'll put the whole Modal view controller class source here.
class ModalViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.present(createAlert(), animated: true, completion: nil)
}
func createAlert() -> UIAlertController {
let actions: [UIAlertAction] = [
UIAlertAction(title: "Something", style: .default, handler: { _ in
print("Something")
}),
UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
print("Cancel")
})
]
let alert = UIAlertController(title: "Title", message: "Message", preferredStyle: .actionSheet)
for action in actions {
alert.addAction(action)
}
return alert
}
}
The line is wrong.
let alert = UIAlertController(title: nil, message: "message" , preferredStyle: .actionSheet, actions: actions)
You must be get error like that
Type of expression is ambiguous without more context
Change it like that:
return UIAlertController(title: YourTitle, message: YourMessage, preferredStyle: UIAlertController.Style.actionSheet)
So it can work.
Need to pass name of the method when you are presenting alert
self.present(createAlert(), animated: true, completion: nil)

create a display alert function globally and call it from any view controller

func displayalert(title:String, message:String, vc:UIViewController)
{
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction((UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
self.dismiss(animated: true, completion: nil)
})))
vc.present(alert, animated: true, completion: nil)
}
this is the function i have used.i tried to call it like this,
displayalert1(title:"dsfvasdcs", message:"easfSDXCSDZX", vc:validateOTPViewController())
it is returning error "BAD ACCESS". the vc.present is running like a loop. I cant understand what the problem is.
I run your code and it working fine. I thing you would pass self in vc.
self.displayalert(title: "Title", message: "Some Message", vc: self)
You can also make an extension of UIViewController-
extension UIViewController {
// Your Function...
}
Now You can globally access this function from any view controller, Just by typing-
self.displayalert(title: "Title", message: "Some Message", vc: self)
You're passing a new instance of the validateOTPViewController to the displayalert function.
Change it to:
displayalert1(title:"dsfvasdcs", message:"easfSDXCSDZX", vc:self)
This will pass the current view controller to the function instead of a new one that hasn't been presented.
Swift 4
Create an extension of UIViewController with your function to display alert with required parameter arguments
extension UIViewController {
func displayalert(title:String, message:String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction((UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
alert.dismiss(animated: true, completion: nil)
})))
self.present(alert, animated: true, completion: nil)
}
}
Now call this function from your view controller:
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.displayalert(title: <String>, message: <String>)
}
}

Dismissing old UIAlertViewController before presenting new UIAlertViewController

I am new to swift, i want to dismiss the alert which is present on
screen when the new alert comes.
I tried:
func showDefaultAlert(controller: UIViewController, title: String, message: String) {
// create the alert
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
// add an action (button)
alert.addAction(UIAlertAction(title: defaultTextForNormalAlertButton, style: UIAlertActionStyle.Default, handler: nil))
// show the alert
//controller.presentViewController(alert, animated: true, completion: nil)
self.showAlert(controller, alert: alert)
}
func showAlert(controller: UIViewController, alert: UIAlertController) {
if let currentPresentedViewController = UIApplication.sharedApplication().keyWindow?.rootViewController?.presentedViewController {
if currentPresentedViewController.isKindOfClass(UIAlertController) {
currentPresentedViewController.dismissViewControllerAnimated(false, completion: {
controller.presentViewController(alert, animated: true, completion: nil)
})
}else {
controller.presentViewController(alert, animated: true, completion: nil)
}
}
}
}
// Call to above method in view controller class:
SPSwiftAlert.sharedObject.showDefaultAlert(self, title:"Title1", message1: "Message")
SPSwiftAlert.sharedObject.showDefaultAlert(self, title:"Title2", message: "Message2")
-
but the above code giving the run time error as :
Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<UIAlertController: 0x7fceb95dcfb0>)
After presenting the UIAlertController you can check its visibility as below:
Presented UIAlertController:
let alerts=UIAlertController(title: "Test", message: "Test", preferredStyle: .Alert)
presentViewController(alerts, animated: true, completion: nil)
Check if the UIAlertController is visible:
if (alerts.isViewLoaded())
{
print("Visible")
//Here you can dismiss the controller
//dismissViewControllerAnimated(true, completion: nil)
}
Check out this demo: Source code

Resources