Present alert after dismissing View Controller - ios

I'm using the newest Xcode and Swift version.
I'm presenting a specific View Controller like this:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let contactViewController = storyboard.instantiateViewController(identifier: "contactViewController")
show(contactViewController, sender: self)
I'm dismissing this View Controller like this:
self.presentingViewController?.dismiss(animated: true, completion: nil)
I want to present an UIAlertController right after dismissing the View Controller.
This:
self.presentingViewController?.dismiss(animated: true, completion: nil)
let alertMessage = UIAlertController(title: "Your message was sent", message: "", preferredStyle: .alert)
let alertButton = UIAlertAction(title: "Okay", style: UIAlertAction.Style.default)
alertMessage.addAction(alertButton)
self.present(alertMessage, animated: true, completion: nil)
… of course doesn't work because I cannot present an UIAlertController on a dismissed View Controller.
What's the best way to present this UIAlertController after the View Controller is dismissed?

You can do it in completion handler by getting top controller like this
self.presentingViewController?.dismiss(animated: true, completion: {
let alertMessage = UIAlertController(title: "Your message was sent", message: "", preferredStyle: .alert)
let alertButton = UIAlertAction(title: "Okay", style: UIAlertAction.Style.default)
alertMessage.addAction(alertButton)
UIApplication.getTopMostViewController()?.present(alertMessage, animated: true, completion: nil)
})
Using this extension
extension UIApplication {
class func getTopMostViewController() -> UIViewController? {
let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
if var topController = keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
} else {
return nil
}
}
}

Use Jawad Ali's extension, we could anchor the current presented ViewController.
And if you want to dismiss that alert later, you could do it in another completion handler as below code showed. In my case, I save a song to one playlist and dismiss this playlist and show a short time alert to let user know that saving ok.
DispatchQueue.main.async {
self?.removeSpinner()
self?.dismiss(animated: true, completion: {
let alert = UIAlertController(title: "Save to playlist", message: nil, preferredStyle: .alert)
UIApplication.getTopMostViewController()?.present(alert, animated: true, completion: {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in
alert.dismiss(animated: true)
}
})
})
}

Related

Warning: Attempt to present <UIAlertController:> on <App name:> whose view is not in the window hierarchy

I'm getting this error because of I'm presenting alert in another VC, but how to solve this.
My code is :
{
let message = res[0]["message"] as! String
//If session expaired move to login page
if message == "Session Expired" {
//Session Expired
DispatchQueue.main.async {
//Remove all navigations
self.navigationController?.viewControllers.removeAll()
//Check whether the user logined or not
UserDefaults.standard.set(false, forKey: "isUserLoggedIn")
//Clear user defaults
SharedClass.sharedInstance.clearDataFromUserDefaults()
let lvc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LVC") as! LoginViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = lvc
}
}
//Call alert function
self.showAlert(title: "", msg: message)
}
Alert function :
//Alert function
extension UIViewController {
func showAlert(title: String, msg: String) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
How to present alert properly in my case....
Try presenting alertController on rootViewController as,
extension UIViewController {
func showAlert(title: String, msg: String) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
(UIApplication.shared.delegate as! AppDelegate).window?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
}
The viewController you are calling showAlert method on is not currently presented viewController in the view hierarchy so either you should get the latest presented viewController in the view hierarchy or try presenting on the rootViewController.
You can check this to get the currently presented viewController.
Just use
self.navigationController?.popToRootViewController(animated: false)
or
self.navigationController?.setViewControllers([LoginViewController], animated: true)
instead of
self.navigationController?.viewControllers.removeAll()

Present new VC when alertviewcontroller is open

I want to display a new ViewController but it's not works when a AlertController is displayed. (I don't want use existing segue, only in swift). Somebody could help me ?
It's just a simple example, in my app i use a custom AlertController who display a loader. So, i don't want to be redirect after click in alert button
Thanks in advance
Ps: Sorry about my bad english.
#IBAction func testButtonClick(_ sender: AnyObject) {
let alert = UIAlertController(title: nil, message: "test", preferredStyle: UIAlertControllerStyle.alert)
self.present(alert, animated: true, completion: nil)
logout()
}
func logout() {
let storyboardName = "Authentication"
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
if let newVC = storyboard.instantiateInitialViewController() {
newVC.modalPresentationStyle = UIModalPresentationStyle.fullScreen
self.present(newVC, animated: true)
} else {
print("Unable to instantiate VC from \(storyboardName) storyboard")
}
}
You can use in this way.
let alert = UIAlertController(title: nil, message: "test", preferredStyle: UIAlertControllerStyle.alert)
self.present(alert, animated: true, completion: nil)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newVC = storyboard.instantiateViewController(withIdentifier: "ChildViewController") as? ChildViewController
newVC?.modalPresentationStyle = UIModalPresentationStyle.fullScreen
presentedViewController?.present(newVC!, animated: true)
One of the options is that you could keep the alert in a var at the instance level, so that as a pre-requisite to your transition, it first closes it.
var alert: UIAlertController?
In testButtonClick:
alert = UIAlertController(title: nil, message: "test", preferredStyle: UIAlertControllerStyle.alert)
let completion = { self.alert = nil }
self.present(alert, animated: true, completion: completion)
In logout:
let completion = { self.alert = nil }
alert?.dismissViewController(animated: true, completion: completion)
Note: you can also make your life easy and use segues and prepareForSegue...

Navigation does not appear Xcode 8.1 iOS 10 Swift 3

Navigation bar does not appear after login button pressed when it moves to Home view controller and I have setup my storyboard and put all thing well but I think i am stuck at this point in code.
Actually there is no segue but coding instead so How I do navigation working?
Code I used to perform Navigation
let vc = self.storyboard?.instantiateViewController(withIdentifier: "Home")
self.present(vc!, animated: true, completion: nil)
#IBAction func loginAction(_ sender: Any) {
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 {
FIRAuth.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)
}
}
}
}
Replace your code:
let vc = self.storyboard?.instantiateViewController(withIdentifier: "Home")
self.present(vc!, animated: true, completion: nil)
With this Code:
let vc : YourViewController = self.storyboard?.instantiateViewController(withIdentifier: "Home")as! YorViewController
let navController = UINavigationController(rootViewController: vc)
self.present(navController, animated: true, completion: nil)
You are just trying to present the view controller with out Navigation. Give it Navigation programmatically then it will show Navigation bar.
Are you really using navigationController? If so, should it be pushViewController rather than present?
navigationController?.pushViewController(vc, animated: true)

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

AlertController is not in the window hierarchy

I've just created a Single View Application project with ViewController class. I would like to show a UIAlertController from a function which is located inside my own class.
Here is my class with an alert.
class AlertController: UIViewController {
func showAlert() {
var alert = UIAlertController(title: "abc", message: "def", preferredStyle: .Alert)
self.presentViewController(alert, animated: true, completion: nil)
}
}
Here is ViewController which executes the alert.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func showAlertButton(sender: AnyObject) {
var alert = AlertController()
alert.showAlert()
}
}
This is what I get instead of a beautiful alert.
Warning: Attempt to present UIAlertController: 0x797d2d20 on Sprint1.AlertController: 0x797cc500 whose view is not in the window hierarchy!
What should I do?
If you're instancing your UIAlertController from a modal controller, you need to do it in viewDidAppear, not in viewDidLoad or you'll get an error.
Here's my code (Swift 4):
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let alertController = UIAlertController(title: "Foo", message: "Bar", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
present(alertController, animated: true, completion: nil)
}
Let's look at your view hierarchy. You have a ViewController.
Then you are creating an AlertController, you are not adding it to your hierarchy and you are calling an instance method on it, that attempts to use the AlertController as presenting controller to show just another controller (UIAlertController).
+ ViewController
+ AlertController (not in hierarchy)
+ UIAlertController (cannot be presented from AlertController)
To simplify your code
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func showAlertButton(sender: AnyObject) {
var alert = UIAlertController(title: "abc", message: "def", preferredStyle: .Alert)
self.presentViewController(alert, animated: true, completion: nil)
}
}
This will work.
If you need the AlertController for something, you will have to add it to the hierarchy first, e.g. using addChildViewController or using another presentViewController call.
If you want the class to be just a helper for creating alert, it should look like this:
class AlertHelper {
func showAlert(fromController controller: UIViewController) {
var alert = UIAlertController(title: "abc", message: "def", preferredStyle: .Alert)
controller.presentViewController(alert, animated: true, completion: nil)
}
}
called as
var alert = AlertHelper()
alert.showAlert(fromController: self)
You can use below function to call alert from any where just include these method in AnyClass
class func topMostController() -> UIViewController {
var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
while ((topController?.presentedViewController) != nil) {
topController = topController?.presentedViewController
}
return topController!
}
class func alert(message:String){
let alert=UIAlertController(title: "AppName", message: message, preferredStyle: .alert);
let cancelAction: UIAlertAction = UIAlertAction(title: "OK", style: .cancel) { action -> Void in
}
alert.addAction(cancelAction)
AnyClass.topMostController().present(alert, animated: true, completion: nil);
}
Then call
AnyClass.alert(message:"Your Message")
Write the following 3 lines, all we need to do is this.
Swift 3.0
private func presentViewController(alert: UIAlertController, animated flag: Bool, completion: (() -> Void)?) -> Void {
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: flag, completion: completion)
}
Swift 2.0
private func presentViewController(alert: UIAlertController, animated flag: Bool, completion: (() -> Void)?) -> Void {
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alert, animated: flag, completion: completion)
}
If you want to create a separate class for displaying alert like this, subclass NSObject not UIViewController.
And pass the ViewControllers reference from which it is initiated, to the showAlert function so that you can present alert view there.
Here is the code of an UIAlertController in a Utility.swift class (not a UIViewController) in Swift3, Thanks Mitsuaki!
private func presentViewController(alert: UIAlertController, animated flag: Bool, completion: (() -> Void)?) -> Void {
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: flag, completion: completion)
}
func warningAlert(title: String, message: String ){
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: { (action) -> Void in
}))
// self.present(alert, animated: true, completion: nil)
presentViewController(alert: alert, animated: true, completion: nil)
}
let alert = UIAlertController(title: "", message: "YOU SUCCESSFULLY\nCREATED A NEW\nALERT CONTROLLER", preferredStyle: .alert)
func okAlert(alert: UIAlertAction!)
{
}
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: okAlert))
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
var rootVC = window?.rootViewController
if var topController = rootVC
{
while let presentedViewController = topController.presentedViewController
{
topController = presentedViewController
}
rootVC = topController
}
rootVC?.present(alert, animated: true, completion: nil)
It helped me to stick a slight delay between the viewDidLoad method and firing the alert method:
[self performSelector:#selector(checkPhotoPermission) withObject:nil afterDelay:0.1f];
This worked for me:
- (UIViewController *)topViewController{
return [self topViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController *)topViewController:(UIViewController *)rootViewController
{
if (rootViewController.presentedViewController == nil) {
return rootViewController;
}
if ([rootViewController.presentedViewController isMemberOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController *)rootViewController.presentedViewController;
UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
return [self topViewController:lastViewController];
}
UIViewController *presentedViewController = (UIViewController *)rootViewController.presentedViewController;
return [self topViewController:presentedViewController];
}
Implementation:
UIViewController * topViewController = [self topViewController];
Using with alert:
[topViewController presentViewController:yourAlert animated:YES completion:nil];
You can send an alert from any class in your app (that uses UIKit: #import <UIKit/UIKit.h> )
Source here.
// I always find it helpful when you want to alert from anywhere it's codebase
// if you find the error above mentioned in the question' title.
let controller = UIAlertController(title: "", message: "Alert!", preferredStyle: UIAlertController.Style.alert)
let action = UIAlertAction(title: "Cancel" , style: UIAlertAction.Style.cancel, handler: nil)
controller.addAction(action)
// Find Root View Controller
var rootVC = UIApplication.shared.windows.first?.rootViewController
if var topController = rootVC {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
rootVC = topController
}
rootVC?.present(controller, animated: true, completion: nil)

Resources