ViewController dismiss swift ios - ios

Hello my english is not very good, I apologize in advance.
I have a problem with my application. The scenario is as follows I have my rootViewController assigned to my ViewController controller which is my start. I have other screens that are a description screen where I have two login and registration buttons which when preloaded bring me to their controller.
Now, when I am on the log full screen form and I send the dismiss order:
ViewController registration
self.dismiss(animated: false, completion: nil)
And all ok the view is hidden but when entering the previous screen that was the description I have a validation if there is already a user if there is the dismiss order:
ViewController Description App
self.dismiss(animated: false, completion: nil)
But it does not perform the action.
Code
UIViewController
class ViewController: UIViewController {
override func viewDidLoad() {
FIRAuth.auth()!.addStateDidChangeListener() { auth, user in
if user == nil {
let descriptionController = DescriptionController()
present(descriptionController, animated: true, completion: nil)
}
}
}
}
DescriptionController
class DescriptionController: UIViewController {
#IBOutlet weak var sign_in_custom: UIButton!
override func viewDidLoad() {
FIRAuth.auth()!.addStateDidChangeListener() { auth, user in
if user != nil {
self.dismiss(animated: false, completion: nil)
}
}
sign_in_custom.addTarget(self, action: #selector(changeToSingIn), for: [.touchUpInside])
}
func changeToSingIn() {
let singInController = SingInController()
present(singInController, animated: true, completion: nil)
}
}
SingInController
class SingInController: UIViewController {
#IBOutlet weak var sign_in_custom: UIButton!
override func viewDidLoad() {
sign_in_custom.addTarget(self, action: #selector(singIn), for: [.touchUpInside])
}
func showLoad() {
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)
alert.view.tintColor = UIColor.black
let loadingIndicator: UIActivityIndicatorView = UIActivityIndicatorView(frame: CGRectMake(10, 5, 50, 50) ) as UIActivityIndicatorView
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)
}
func hideLoad() {
self.dismiss(animated: false, completion: nil)
}
func singIn() {
if (emailVarification()){
if (passwordVarification()){
showLoad()
guard let email = emailTextField.text else { return }
guard let password = passwordTextField.text else { return }
FIRAuth.auth()?.createUser(withEmail: email, password: password) { (user, error) in
hideLoad()
if (user != nil) {
self.dismiss(animated: false, completion: nil)
} else {
let alert = UIAlertController(title: "Error", message: "This is a error", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
} else {
let alert = UIAlertController(title: "Error", message: "This is a error", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
} else {
let alert = UIAlertController(title: "Error", message: "This is a error", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
sequence

This is because the second controller has basically just been placed on the top of the existing one. The first view is still running under the second view, and when the second view is dismissed the first view won't call ViewDidLoad. So to solve it, you probably want to add it inside the ViewDidAppear Function.

Use this code in ViewdidAppear:
FIRAuth.auth()!.addStateDidChangeListener() { auth, user in
if user != nil {
self.dismiss(animated: false, completion: nil)
}
}
sign_in_custom.addTarget(self, action: #selector(changeToSingIn), for: [.touchUpInside])

Instead of having the DescriptionController dismiss itself, a better way would be for it would be to instead inform the ViewController that the user has signed in and also returns any errors, if necessary. Then the ViewController can perform any additional steps needed based on successful or failed sign-in. This could be accomplished by using the delegate pattern (DescriptionController defines a protocol and ViewController implements it).

Related

Attempt to present UIAlertController whose view is not in the window hierarchy - Swift Error

I'm trying to create an alert so when a user signs up and then wants to log back in they can be warned if the password is wrong because at the moment it just performs the segue and the attempt to alert fails. I'm using Firebase so the password that is entered into firebase on sign up should be the one users log in with.
import Foundation
import UIKit
import Firebase
class SignInViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var EmailAddressTextField: UITextField!
#IBOutlet weak var PasswordTextField: UITextField!
// Do any additional setup after loading the view.
override func viewDidLoad() {
super.viewDidLoad()
EmailAddressTextField.delegate = self
PasswordTextField.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
#IBAction func LogInButton(_ sender: Any) {
if (EmailAddressTextField.text != "" && PasswordTextField.text != ""){
Auth.auth().signIn(withEmail: EmailAddressTextField.text!, password: PasswordTextField.text!) { user, error in
if error == nil {
self.performSegue(withIdentifier: "LogInSegue", sender: nil)
} else {
let alert = UIAlertController(title: "Error", message: "Enter a correct email and password", preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
}
}
}
}
Just want to check something with you, as your description above is a little unclear. When you enter the wrong email/password combination, does your app still perform the "LogInSegue" (as well as failing to show the alert)?
If so, it sounds like you might have connected your segue to the UIButton instead of the UIViewController.
To check this, click on your segue in the Storyboard and see if the UIButton is highlighted.
If it is, then delete this segue and follow the instruction below to connect your new segue from the view controller:
You are presenting UIAlertController on SignInViewController so at that time SignInViewController is not in navigation stack.
So when you are presenting UIAlertController at that time check the self.navigationController?.viewControllers and verify that it is in stack or not.
for eg.
//check before self.presnt...
print(self.navigationController?.viewControllers)
So you need to make sure that when you are presenting a view controller over another view controller that must be in navigation stack otherwise you will get this message in log.
Attempt to present UIAlertController whose view is not in the window hierarchy
Use the following function to show alert over root view controller
func showAlert(title: String, msg: String, actions:[UIAlertAction]?) {
var actions = actions
let alertVC = UIAlertController(title: title, message: msg, preferredStyle: .alert)
if actions == nil {
actions = [UIAlertAction(title: "OK", style: .default, handler: nil)]
}
for action in actions! {
alertVC.addAction(action)
}
if let rootVC = UIApplication.shared.delegate?.window??.rootViewController {
rootVC.present(alertVC, animated: true, completion: nil)
} else {
print("Root view controller is not set.")
}
}
Usage
self.showAlert(title: "My App", msg: "your message", actions: nil)
You need to use this with your code like this...
class SignInViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var EmailAddressTextField: UITextField!
#IBOutlet weak var PasswordTextField: UITextField!
// Do any additional setup after loading the view.
override func viewDidLoad() {
super.viewDidLoad()
EmailAddressTextField.delegate = self
PasswordTextField.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
#IBAction func LogInButton(_ sender: Any) {
if (EmailAddressTextField.text != "" && PasswordTextField.text != ""){
Auth.auth().signIn(withEmail: EmailAddressTextField.text!, password: PasswordTextField.text!) { user, error in
if error == nil {
self.performSegue(withIdentifier: "LogInSegue", sender: nil)
} else {
// let alert = UIAlertController(title: "Error", message: "Enter a correct email and password", preferredStyle: .alert)
// let action = UIAlertAction(title: "OK", style: .default, handler: nil)
// alert.addAction(action)
// self.present(alert, animated: true, completion: nil)
//use like this...
self.showAlert(title: "Error", msg: "Enter a correct email and password", actions: nil)
}
}
}
}
//###############################################
func showAlert(title: String, msg: String, actions:[UIAlertAction]?) {
var actions = actions
let alertVC = UIAlertController(title: title, message: msg, preferredStyle: .alert)
if actions == nil {
actions = [UIAlertAction(title: "OK", style: .default, handler: nil)]
}
for action in actions! {
alertVC.addAction(action)
}
if let rootVC = UIApplication.shared.delegate?.window??.rootViewController {
rootVC.present(alertVC, animated: true, completion: nil)
} else {
print("Root view controller is not set.")
}
}
//###############################################
}
Present it in main activity :
dispatch_async(dispatch_get_main_queue(), ^{
let alert = UIAlertController(title: "Error", message: "Enter a correct email and password", preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
});

When I try to send a text message through my app the Messages screen is automatically cancelled

I am tying to have my app take a users input to format a text message so that they can connect to an external device in the proper format through text. I have the initial setup text message working but if the user wishes to delete the geo-fence (the initial text message is responsible for setting it) they need to send a different text message however the Messages screen is automatically cancelled when called.
#IBAction func addGeoFencePushed(_ sender: Any) {
if(!geoFenceIsEnabled()){
let alertController = UIAlertController(title: nil, message: "How large would you like the geofence to be?", preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "Confirm", style: .default) { (_) in
if let field = alertController.textFields?[0] {
// store your data
ALGlobal.sharedInstance.globalDefaults.set(field.text! as String, forKey: "geoFenceSize")
let rad = Double(field.text!)
ALGlobal.sharedInstance.geoFenceRadius = rad!
self.buttonSetting = 1
self.toggleButton()
self.drawGeoFence(radius: rad!)
self.turnOnFenceText(radius: Int(rad!))
} else {
// user did not fill field
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
alertController.addTextField { (textField) in
textField.placeholder = "Feet"
textField.keyboardType = .numberPad
}
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
else{
// create the alert
let alert = UIAlertController(title: "Confirm", message: "Are you sure you want to remove the geo-fence?", preferredStyle: UIAlertControllerStyle.alert)
let confirmAction = UIAlertAction(title: "Confirm", style: .default) { (_) in
alert.dismiss(animated: true, completion: nil)
self.removeGeoFence()
}
alert.addAction(confirmAction)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
// show the alert
self.present(alert, animated: true, completion: nil)
}
}
This is what is responsible for determining which text message to display.
func removeGeoFence(){
self.turnOffFenceText()
ALGlobal.sharedInstance.globalDefaults.removeObject(forKey: "geoFenceSize")
ALGlobal.sharedInstance.geoFenceRadius = 0
self.buttonSetting = 0
self.toggleButton()
self.clearMap()
}
func turnOnFenceText(radius: Int){
let radiusConverted = Int(radius / 3)
messageVC.body = "G1,1,0,\(radiusConverted)M"
messageVC.recipients = [ALGlobal.sharedInstance.globalDefaults.object(forKey: "devicePhoneNumber") as! String]
self.present(messageVC, animated: true, completion: nil)
}
func turnOffFenceText(){
messageVC.body = "G1,0"
messageVC.recipients = [ALGlobal.sharedInstance.globalDefaults.object(forKey: "devicePhoneNumber") as! String]
self.present(messageVC, animated: true, completion: nil)
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
switch (result.rawValue) {
case MessageComposeResult.cancelled.rawValue:
print("Message was cancelled")
messageVC.dismiss(animated: true, completion: nil)
case MessageComposeResult.failed.rawValue:
print("Message failed")
messageVC.dismiss(animated: true, completion: nil)
case MessageComposeResult.sent.rawValue:
print("Message was sent")
messageVC.dismiss(animated: true, completion: nil)
//self.dismiss(animated: true, completion: nil)
default:
break;
}
}
And these are the message controllers. When removeGeoFence() is called the console prints out "Message was cancelled" and the Messages app never opens and I can't figure out why.
You need to recreate the messageVC after you dismiss it otherwise your trying to reopen a dismissed view and that results in a black screen so in the turnOnFenceText() and turnOffFenceText() I reinitialized the messageVC:
func turnOnFenceText(radius: Int){
messageVC = MFMessageComposeViewController()
messageVC.messageComposeDelegate = self;
let radiusConverted = Int(radius / 3)
messageVC.body = "G1,1,0,\(radiusConverted)M"
messageVC.recipients = [ALGlobal.sharedInstance.globalDefaults.object(forKey: "devicePhoneNumber") as! String]
self.present(messageVC, animated: true, completion: nil)
}
func turnOffFenceText(){
messageVC = MFMessageComposeViewController()
messageVC.messageComposeDelegate = self;
messageVC.body = "G1,0"
messageVC.recipients = [ALGlobal.sharedInstance.globalDefaults.object(forKey: "devicePhoneNumber") as! String]
self.present(messageVC, animated: true, completion: nil)
}
That fixed the problem

Adding Activity Indicator after Clicking "LOGIN"?

I would like to add an Activity Indicator for my Login VC so that users will see that spinner thing once they click the "login" button. I have done multiple attempts and failed. Even if I put in codes for hiding the activity indicator, it just keeps animating even before clicking the "login" button. I deleted those codes, and have my original codes below (without activity indicator).
import UIKit
import Firebase
class LoginViewController: UIViewController {
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView = UIImageView(frame: view.bounds)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.image = #imageLiteral(resourceName: "background")
imageView.center = view.center
view.addSubview(imageView)
self.view.sendSubview(toBack: imageView)
}
//Outlets
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
//Login Action
#IBAction func loginAction(_ sender: AnyObject) {
if self.emailTextField.text == "" || self.passwordTextField.text == "" {
//Alert to tell the user that there was an error because they didn't fill anything in the textfields because they didn't fill anything in
let alertController = UIAlertController(title: "Error", message: "Please enter an email and password.", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
} else {
Auth.auth().signIn(withEmail: self.emailTextField.text!, password: self.passwordTextField.text!) { (user, error) in
if error == nil {
//Print into the console if successfully logged in
print("You have successfully logged in")
//Go to the HomeViewController if the login is sucessful
let vc = self.storyboard?.instantiateViewController(withIdentifier: "Home")
self.present(vc!, animated: true, completion: nil)
} else {
//Tells the user that there is an error and then gets firebase to tell them the error
let alertController = UIAlertController(title: "Error", message: error?.localizedDescription, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
}
}
}
so I know the first step is probably dragging the activity indicator to the VC in Storyboard, but what's next?
You need to create a IBOutlet of dragged UIActivityIndicator. Then in viewDidLoadfunc hide this UIActivityIndicator with it's IBOutlet. When you click on Login Button, then unhide this activityIndicator and hide again, once receive response from login.
Create an IBOUtlet of your activity indicator from Storyboard to your Viewcontroller -
You can then in your ViewDidLoad or your storyboard set the below property
activityIndicator.hidesWhenStopped = true;
And when you want to start it, call
activityIndicator.startAnimating();
And to stop it from animating -
activityIndicator.stopAnimating();
The same way you created your IBOutlets of UITextField, create one with your UIActivityIndicator. Make sure your indicator's hidesWhenStopped is set to true in the storyboard.
Then animate it before calling your signin method, and stop it on the completion handler
#IBOutlet weak var activityIndicator: UIActivityIndicator!
//...
activityIndicator.startAnimating()
Auth.auth().signIn(withEmail: self.emailTextField.text!, password: self.passwordTextField.text!) { (user, error) in {
activityIndicator.stopAnimating()
//...
}
You can create UIActivityIndicatorView in your class programmatically & customize it in viewDidLoad
var activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
// Add below code in viewDidLoad
self.activityIndicator.hidesWhenStopped = true
self.activityIndicator.center = view.center
self.view.addSubView(self.activityIndicator)
Now do start & stop animating whereever you need
//Login Action
#IBAction func loginAction(_ sender: AnyObject) {
self.activityIndicator.startAnimating()
if self.emailTextField.text == "" || self.passwordTextField.text == "" {
//Alert to tell the user that there was an error because they didn't fill anything in the textfields because they didn't fill anything in
let alertController = UIAlertController(title: "Error", message: "Please enter an email and password.", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
} else {
Auth.auth().signIn(withEmail: self.emailTextField.text!, password: self.passwordTextField.text!) { (user, error) in
self.activityIndicator.stopAnimating()
if error == nil {
//Print into the console if successfully logged in
print("You have successfully logged in")
//Go to the HomeViewController if the login is sucessful
let vc = self.storyboard?.instantiateViewController(withIdentifier: "Home")
self.present(vc!, animated: true, completion: nil)
} else {
//Tells the user that there is an error and then gets firebase to tell them the error
let alertController = UIAlertController(title: "Error", message: error?.localizedDescription, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
}
}
In your storyboard, you can find checkbox.
startsAnimating
HidesWhenStops(check this in your storyboard.)
#IBOutlet weak var activityIndicator: UIActivityIndicator!
#IBAction func loginAction(_ sender: AnyObject) {
activityIndicator.startAnimating()
if self.emailTextField.text == "" || self.passwordTextField.text == "" {
//Alert to tell the user that there was an error because they didn't fill anything in the textfields because they didn't fill anything in
let alertController = UIAlertController(title: "Error", message: "Please enter an email and password.", preferredStyle: .alert)
activityIndicator.stopAnimating()
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
} else {
Auth.auth().signIn(withEmail: self.emailTextField.text!, password: self.passwordTextField.text!) { (user, error) in
if error == nil {
//Print into the console if successfully logged in
print("You have successfully logged in")
activityIndicator.stopAnimating()
//Go to the HomeViewController if the login is sucessful
let vc = self.storyboard?.instantiateViewController(withIdentifier: "Home")
self.present(vc!, animated: true, completion: nil)
} else {
//Tells the user that there is an error and then gets firebase to tell them the error
let alertController = UIAlertController(title: "Error", message: error?.localizedDescription, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
}
}
}
An alternative Approach. Adding the UIActivityViewController programatically:
In the LoginViewController class add
let myActivityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
In the viewDidLoad() add the following
myActivityIndicator.hidesWhenStopped = true
myActivityIndicator.center = view.center
view.addSubview(myActivityIndicator)
In #IBAction func loginAction(_ sender: AnyObject) in the else part
add
activityIndicator.startAnimating()
Auth.auth().signIn(withEmail: self.emailTextField.text!, password: self.passwordTextField.text!) { (user, error) in {
activityIndicator.stopAnimating()
I have written a class to use progress hud properly. You just need to drag and drop the class to your project...
https://github.com/emraz/ERProgressHud
For showing progress hud write ..
ERProgressHud.show()
For hiding progress hud write ..
ERProgressHud.hide()
In your code ..
//Login Action
#IBAction func loginAction(_ sender: AnyObject) {
if self.emailTextField.text == "" || self.passwordTextField.text == "" {
//Alert to tell the user that there was an error because they didn't fill anything in the textfields because they didn't fill anything in
let alertController = UIAlertController(title: "Error", message: "Please enter an email and password.", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
return
}
else {
ERProgressHud.show()
Auth.auth().signIn(withEmail: self.emailTextField.text!, password: self.passwordTextField.text!) { (user, error) in
ERProgressHud.hide()
if error == nil {
//Print into the console if successfully logged in
print("You have successfully logged in")
//Go to the HomeViewController if the login is sucessful
let vc = self.storyboard?.instantiateViewController(withIdentifier: "Home")
self.present(vc!, animated: true, completion: nil)
} else {
//Tells the user that there is an error and then gets firebase to tell them the error
let alertController = UIAlertController(title: "Error", message: error?.localizedDescription, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
}
}
}

button action does not work for the first time tapped

I have created a button in my storyboard. And created an action and outlet for it. I do some tasks in action of a button. But the button action is performed only from the second time.
Here's my code for the button action:
#IBAction func sendButtonTapped(sender: UIButton) {
let domain = String(txtEmail.text!.characters.suffix(9))
if txtEmail.text == "" || domain != "nu.edu.kz" {
let alertController = UIAlertController(title: "Error", message: "Please enter your valid nu.edu.kz e-mail", preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "Ok", style: .Cancel, handler: nil)
alertController.addAction(defaultAction)
self.presentViewController(alertController, animated: true, completion: nil)
} else {
if sendButton.currentTitle != "Reset Password has been sent" {
FIRAuth.auth()?.sendPasswordResetWithEmail(self.txtEmail.text!, completion: { (error) in
print(error?.localizedDescription)
if error != nil {
print(error?.localizedDescription)
} else {
print("reset password email has been sent")
self.sendButton.setTitle("Reset Password has been sent", forState: .Normal)
}
})
}
}
}
And as I said before I have created an outlet for my button and textfield:
#IBOutlet weak var sendButton: UIButton!
#IBOutlet weak var txtEmail: UITextField!
P.S. I use Swift 2.3 here. Actually, I want to setTitle of the button to the new one. And this changes after tapping for the second time.
Please refer to this:
presentViewController:animated:YES view will not appear until user taps again
Probably want you want to do is call presentViewController(alertController, animated: true, completion: nil)
explicitly on main thread
dispatch_async(dispatch_get_main_queue()) {
self.presentViewController(alertController, animated: true, completion: nil)
}

Dismiss all UIAlertControllers currently presented

Is there a way to dismiss all UIAlertControllers that are currently presented?
This is specifically because from anywhere and any state of my app, I need to get to a certain ViewController when a push notification is pressed.
func dismissAnyAlertControllerIfPresent() {
guard let window :UIWindow = UIApplication.shared.keyWindow , var topVC = window.rootViewController?.presentedViewController else {return}
while topVC.presentedViewController != nil {
topVC = topVC.presentedViewController!
}
if topVC.isKind(of: UIAlertController.self) {
topVC.dismiss(animated: false, completion: nil)
}
}
This worked for me!
Edit: for iOS 13+
func dismissAnyAlertControllerIfPresent() {
guard let window = windows.first(where: { $0.isKeyWindow }),
var topVC = window.rootViewController?.presentedViewController else {return}
while topVC.presentedViewController != nil {
topVC = topVC.presentedViewController!
}
if topVC.isKind(of: UIAlertController.self) {
topVC.dismiss(animated: false, completion: nil)
}
}
You could subclass your UIAlertControllers, attach NSNotification observers to each which would trigger a method within the UIAlertController subclass to dismiss the alert controller, then post an NSNotification whenever you're ready to dismiss, ex:
class ViewController: UIViewController {
func presentAlert() {
// Create alert using AlertController subclass
let alert = AlertController(title: nil, message: "Message.", preferredStyle: UIAlertControllerStyle.Alert)
// Add observer to the alert
NSNotificationCenter.defaultCenter().addObserver(alert, selector: Selector("hideAlertController"), name: "DismissAllAlertsNotification", object: nil)
// Present the alert
self.presentViewController(alert, animated: true, completion:nil)
}
}
// AlertController subclass with method to dismiss alert controller
class AlertController: UIAlertController {
func hideAlertController() {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
Then post the notification whenever you're ready to dismiss the alert (in this case, when the push notification is pressed):
NSNotificationCenter.defaultCenter().postNotificationName("DismissAllAlertsNotification", object: nil)
You can dismiss an UIAlertController that is currently being presented to the user this way:
self.dismissViewControllerAnimated(true, completion: nil)
I have written more generic code in swift 4,
This class shows alerts using utility class.
import UIKit
let APP_ORANGE_COLOR = UIColor(red: 1.000, green: 0.412, blue: 0.000, alpha: 1.00)
extension UIAlertController {
#objc func hideAlertController() {
self.dismiss(animated: false, completion: nil)
}
}
class AlertUtility: UIViewController {
static func showAlert(title: String!, message : String!, viewController: UIViewController) {
let alert = UIAlertController(title: title, message: message ,preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: "ok"), style: UIAlertActionStyle.cancel, handler: nil))
alert.view.tintColor = APP_ORANGE_COLOR
NotificationCenter.default.addObserver(alert, selector: #selector(alert.hideAlertController), name: DismissAllAlertsNotification, object: nil)
viewController.present(alert, animated: true, completion: nil)
}
static func showAlertAutoDismiss(title: String!, message : String!) -> Void {
//let appDelegate = UIApplication.shared.delegate as! AppDelegate
// the alert view
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let topWindow = UIWindow(frame: UIScreen.main.bounds)
topWindow.rootViewController = UIViewController()
topWindow.windowLevel = UIWindowLevelAlert + 0.8
topWindow.makeKeyAndVisible()
topWindow.rootViewController?.present(alert, animated: true, completion: {})
// change to desired number of seconds (in this case 5 seconds)
let when = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: when){
// your code with delay
alert.dismiss(animated: true, completion: nil)
topWindow.isHidden = true
}
}
static func showAlert(title: String!, message : String!) -> Void {
//let appDelegate = UIApplication.shared.delegate as! AppDelegate
// the alert view
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let topWindow = UIWindow(frame: UIScreen.main.bounds)
topWindow.rootViewController = UIViewController()
topWindow.windowLevel = UIWindowLevelAlert + 1
topWindow.makeKeyAndVisible()
topWindow.rootViewController?.present(alert, animated: true, completion: {})
alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: "ok"), style: UIAlertActionStyle.cancel, handler: {(_ action: UIAlertAction) -> Void in
// continue your work
// important to hide the window after work completed.
// this also keeps a reference to the window until the action is invoked.
topWindow.isHidden = true
}))
NotificationCenter.default.addObserver(alert, selector: #selector(alert.hideAlertController), name: DismissAllAlertsNotification, object: nil)
alert.view.tintColor = APP_ORANGE_COLOR
}
static func showGenericErrorMessageAlert(viewController: UIViewController) {
let alert = UIAlertController(title: NSLocalizedString("error", comment: ""), message: NSLocalizedString("generic.error.message", comment: "") ,preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: "ok"), style: UIAlertActionStyle.cancel, handler: nil))
alert.view.tintColor = APP_ORANGE_COLOR
viewController.present(alert, animated: true, completion: nil)
NotificationCenter.default.addObserver(alert, selector: #selector(alert.hideAlertController), name: DismissAllAlertsNotification, object: nil)
}
static func showComingSoonAlert(viewController: UIViewController) {
// the alert view
let alert = UIAlertController(title: "", message: NSLocalizedString("coming.soon", comment: ""), preferredStyle: .alert)
viewController.present(alert, animated: true, completion: {})
// change to desired number of seconds (in this case 5 seconds)
let when = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: when){
// your code with delay
alert.dismiss(animated: true, completion: nil)
}
NotificationCenter.default.addObserver(alert, selector: #selector(alert.hideAlertController), name: DismissAllAlertsNotification, object: nil)
}
// Show alert view with call back
static func showAlertWithCB(title: String, message: String, isConditional: Bool, viewController: UIViewController, completionBlock: #escaping (_: Bool) -> Void) {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.view.tintColor = APP_ORANGE_COLOR
// Check whether it's conditional or not ('YES' 'NO, or just 'OK')
if isConditional
{
alert.addAction(UIAlertAction(title: NSLocalizedString("cancel", comment: ""), style: UIAlertActionStyle.cancel, handler: { (action: UIAlertAction) in
alert.dismiss(animated: true, completion: nil)
completionBlock(false)
}))
alert.addAction(UIAlertAction(title: NSLocalizedString("yes", comment: ""), style: UIAlertActionStyle.default, handler: { (action: UIAlertAction) in
alert.dismiss(animated: true, completion: nil)
completionBlock(true)
}))
}
else
{
alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: "ok"), style: UIAlertActionStyle.default, handler: { (action: UIAlertAction) in
alert.dismiss(animated: true, completion: nil)
completionBlock(true)
}))
}
NotificationCenter.default.addObserver(alert, selector: #selector(alert.hideAlertController), name: DismissAllAlertsNotification, object: nil)
viewController.present(alert, animated: true, completion: nil)
}
static func showAlertWithTextField(viewController : UIViewController,completionBlock: #escaping (_: Bool, String) -> Void) {
//1. Create the alert controller.
let alert = UIAlertController(title: "Report Event?", message: "", preferredStyle: .alert)
alert.view.tintColor = APP_ORANGE_COLOR
//2. Add the text field. You can configure it however you need.
//AlertUtility.addte
alert.addTextField { (textField) in
let heightConstraint = NSLayoutConstraint(item: textField, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 50)
textField.addConstraint(heightConstraint)
textField.placeholder = "Enter report reason here"
textField.tintColor = APP_ORANGE_COLOR
textField.autocapitalizationType = .sentences
}
// 3. Grab the value from the text field, and print it when the user clicks OK.
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { [weak alert] (_) in
// Force unwrapping because we know it exists.
completionBlock(true,"")
//print("Text field: \(textField.text)")
}))
// 3. Grab the value from the text field, and print it when the user clicks OK.
alert.addAction(UIAlertAction(title: "Submit", style: .default, handler: { [weak alert] (_) in
let textField = alert?.textFields![0] // Force unwrapping because we know it exists.
completionBlock(true,(textField?.text)!)
//print("Text field: \(textField.text)")
}))
// 4. Present the alert.
viewController.present(alert, animated: true, completion: nil)
let textField = alert.textFields![0]
let v = UIView.init(frame: textField.frame)
textField.addSubview(v)
v.frame = textField.frame
v.bounds = textField.bounds
v.backgroundColor = APP_ORANGE_COLOR
v.superview?.bringSubview(toFront: v)
}
}
Use it in this way
//sample code - use in your view controller
AlertUtility.showAlertWithCB(title: NSLocalizedString("alert", comment: "") , message: (error)!, isConditional: false, viewController: self, completionBlock: { (yes) in
//Your actions on callback
self.popToPreviousController()
})
AlertUtility.showAlert(title: ALERT_TITLE, message: message, viewController: self)
Post notification when you want/need to auto dismiss alerts in app
let DismissAllAlertsNotification = Notification.Name("DismissAllAlertsNotification")
NotificationCenter.default.post(name: DismissAllAlertsNotification, object: nil)
They are modal: there will only be one of them at any time and it will have full focus.
It is your responsibility to create your alerts and it is the user that should be dismissing them. You need to make (or use) a custom view to display a stack of messages if you need further control.
I used the following extension to achieve this, I hope it may help you
First; you will have an extension for UIApplication to retrieve the RootViewController
extension UIApplication {
static func topViewControllerInNavigationStack(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewControllerInNavigationStack(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewControllerInNavigationStack(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewControllerInNavigationStack(controller: presented)
}
return controller
}
}
Second; extension for UIAlertViewController
extension UIAlertController {
static func dismissPresentedAlertViewController() {
let viewController = UIApplication.topViewControllerInNavigationStack()
guard let isKindOf = viewController?.isKind(of:
UIAlertController.classForCoder()), isKindOf else {
return
}
viewController?.dismiss(animated: false, completion: nil)
}
}

Resources