Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Present UIAlertController in the currently active UIViewController

I've got a timer showing an alert when finished. This alert view should be presented in the view controller which the user is currently in.

My feeling is this could be accomplished much more effective than the following:

The way I'm doing this now is give an observer for a notification to each of my 5 view controllers as well as a method to create and present that alert.

Is there a way to only set up the alert once and then present it in the view controller that is currently active?

Here's my code:

// I've got the following in each of my view controllers.

// In viewDidLoad()
override func viewDidLoad() {
  super.viewDidLoad()
  NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(SonglistViewController.presentSleepTimerFinishedAlert(_:)), name: "presentSleepTimerFinishedAlert", object: nil)
}


func presentTimerFinishedAlert(notification: NSNotification) {
  let alertController = UIAlertController(title: "Timer finished", message: nil, preferredStyle: UIAlertControllerStyle.Alert)
  alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
  presentViewController(alertController, animated: true, completion: nil)
}

Thanks a lot for any ideas!

like image 796
nontomatic Avatar asked Feb 07 '23 08:02

nontomatic


2 Answers

extension UIApplication {
    /// The top most view controller
    static var topMostViewController: UIViewController? {
        return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
    }
}

extension UIViewController {
    /// The visible view controller from a given view controller
    var visibleViewController: UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.visibleViewController
        } else if let tabBarController = self as? UITabBarController {
            return tabBarController.selectedViewController?.visibleViewController
        } else if let presentedViewController = presentedViewController {
            return presentedViewController.visibleViewController
        } else {
            return self
        }
    }
}

With this you can easily present your alert like so

UIApplication.topMostViewController?.present(alert, animated: true, completion: nil)

One thing to note is that if there's a UIAlertController currently being displayed, UIApplication.topMostViewController will return a UIAlertController. Presenting on top of a UIAlertController has weird behavior and should be avoided. As such, you should either manually check that !(UIApplication.topMostViewController is UIAlertController) before presenting, or add an else if case to return nil if self is UIAlertController

extension UIViewController {
    /// The visible view controller from a given view controller
    var visibleViewController: UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.visibleViewController
        } else if let tabBarController = self as? UITabBarController {
            return tabBarController.selectedViewController?.visibleViewController
        } else if let presentedViewController = presentedViewController {
            return presentedViewController.visibleViewController
        } else if self is UIAlertController {
            return nil
        } else {
            return self
        }
    }
}
like image 72
NSExceptional Avatar answered Feb 13 '23 06:02

NSExceptional


You can find the Top ViewController on the navigation stack and directly present the AlertController from there. You can use the extension method posted here to find the Top ViewController from anywhere in your application:

https://stackoverflow.com/a/30858591/2754727

like image 36
pnavk Avatar answered Feb 13 '23 05:02

pnavk