44

I'm working with PushNotification on iOS app. I would like to show a UIalertcontroller when the app receive a notification.

I try this code below in the AppDelegate:

[self.window.rootViewController presentViewController:alert animated:YES completion:nil];

But the UIAlertcontroller is showing in the root View (First screen) and for other uiviewcontroller i got warning or the app crashes.

Anbu.Karthik
  • 82,064
  • 23
  • 174
  • 143
Rockers23
  • 775
  • 1
  • 12
  • 24
  • what the creash report – Anbu.Karthik Mar 22 '16 at 13:37
  • But the UIAlertcontroller is showing in the root View ....ofcourse you are adding the alert to root controller. OFcourse it will crash on other uiview controller because you are ttrying to add alert on controller which in not shown to user. – Teja Nandamuri Mar 22 '16 at 13:43
  • 1
    Yes i know that i 'm adding uialertcontroller to the rootView and not to active view and my question is how can'i show the uialertController in the other uiviewcontroller when notification is received. – Rockers23 Mar 22 '16 at 14:00

7 Answers7

91

try this

Objective-C

UIWindow* topWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
topWindow.rootViewController = [UIViewController new];
topWindow.windowLevel = UIWindowLevelAlert + 1;

UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"APNS" message:@"received Notification" preferredStyle:UIAlertControllerStyleAlert];

[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK",@"confirm") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
    // 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.hidden = YES; // if you want to hide the topwindow then use this
    topWindow = nil; // if you want to remove the topwindow then use this 
}]];

[topWindow makeKeyAndVisible];
[topWindow.rootViewController presentViewController:alert animated:YES completion:nil];

Swift3 and above

var topWindow: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
topWindow?.rootViewController = UIViewController()
topWindow?.windowLevel = UIWindow.Level.alert + 1

let alert = UIAlertController(title: "APNS", message: "received Notification", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel) { _ 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 // if you want to hide the topwindow then use this
    topWindow = nil // if you want to hide the topwindow then use this
 })

topWindow?.makeKeyAndVisible()
topWindow?.rootViewController?.present(alert, animated: true, completion: nil)

Detail description: http://www.thecave.com/2015/09/28/how-to-present-an-alert-view-using-uialertcontroller-when-you-dont-have-a-view-controller/

Carmen
  • 6,177
  • 1
  • 35
  • 40
Anbu.Karthik
  • 82,064
  • 23
  • 174
  • 143
  • How would i go about adding another button to UIAlert for swift 3 – Johny D Good Sep 04 '17 at 13:40
  • create the object of UIAlertAction and handle the action – Anbu.Karthik Sep 04 '17 at 13:42
  • @JohnyDGood - see this for multiple action https://stackoverflow.com/questions/35152650/how-to-add-button-in-uialertcontroller-in-ios-9/35152847#35152847 – Anbu.Karthik Sep 04 '17 at 13:43
  • @Anbu.karthik I guess making a new window to showing an alert is not good approach and code is just hiding the window not completely removing it.. Could you please correct me if I'm wrong? – TheTiger Apr 05 '18 at 07:19
  • @TheTiger- thanks my senior for the valuable point, I updated the answer, based on the ARC concept I answered – Anbu.Karthik Apr 05 '18 at 07:24
  • @TheTiger - is there any alternate way to remove. – Anbu.Karthik Apr 05 '18 at 07:34
  • I'm not your senior at all:) I guess setting `nil` will be fine. But you will need to take window object as `var` and `optional`. `var topWindow: UIWindow? = UIWindow(frame: UIScreen.main.bounds)` – TheTiger Apr 05 '18 at 07:52
  • @TheTiger - thank you I created the optional window for remove – Anbu.Karthik Apr 05 '18 at 07:56
  • @Anbu.karthik i have one doubt in ios 11+ where i can do the code in appdelegate for posting the recieved FCM notification in foreground state. Getting FCM notification in iphone 5 and not in iphone 8 – Farsheel May 12 '18 at 06:19
  • I used this approach and works fine! The only problem is that if the controller you are currently showing hides the status bar, when you present the alert, you have the status bar visible until you dismiss it. It's only a small problem but it's there. I don't know if's possible to hide the status bar also in the new window. – Luca Mar 31 '22 at 09:14
17

Shortest & Simplest :

Create a extension :

extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
    if let navigationController = controller as? UINavigationController {
        return topViewController(controller: navigationController.visibleViewController)
    }
    if let tabController = controller as? UITabBarController {
        if let selected = tabController.selectedViewController {
            return topViewController(controller: selected)
        }
    }
    if let presented = controller?.presentedViewController {
        return topViewController(controller: presented)
    }
    return controller
} }

and then use it anywhere like

UIApplication.topViewController()?.present(UIViewController, animated: true, completion: nil)

With this you can present Alert or anything Anywhere Example :

let alert = UIAlertController(title: "Your title", message: "Your message", preferredStyle: .alert)
let cancelButton = UIAlertAction(title: "Ok", style: .cancel, handler: nil)
alert.addAction(cancelButton)
UIApplication.topViewController()?.present(alert, animated: true, completion: nil)

ALTERNATE METHOD :

No need to create any Extension or any method or anything simply write the above 3 lines for creating an Alert and for presenting use :

self.window?.rootViewController?.present(alert, animated: true, completion: nil)

That's it.! =)

Yash Bedi
  • 1,323
  • 17
  • 25
9

Anbu.Karthik's answer but in Swift 4.1

var topWindow: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
topWindow?.rootViewController = UIViewController()
topWindow?.windowLevel = UIWindowLevelAlert + 1
let alert: UIAlertController =  UIAlertController(title: "APNS", message: "received Notification", preferredStyle: .alert)
    alert.addAction(UIAlertAction.init(title: "OK", style: .default, handler: { (alertAction) in
        topWindow?.isHidden = true
        topWindow = nil
    }))

topWindow?.makeKeyAndVisible()
topWindow?.rootViewController?.present(alert, animated: true, completion:nil)

Thanks for reading this.

Najam
  • 1,129
  • 11
  • 32
5

Swift 4.1 You can use the following code to present alert from AppDelegate

func showAlertFromAppDelegates(){
    let alertVC = UIAlertController(title: "Oops" , message: "Presented Alert from AppDelegates", preferredStyle: UIAlertControllerStyle.alert)
    let okAction = UIAlertAction(title: "Okay", style: UIAlertActionStyle.cancel) { (alert) in
        exit(0) // Your code here
    }
    alertVC.addAction(okAction)
    DispatchQueue.main.async {
        var presentVC = self.window?.rootViewController
        while let next = presentVC?.presentedViewController {
            presentVC = next
        }
        presentVC?.present(alertVC, animated: true, completion: nil)
    }
}
Rahul Panzade
  • 1,302
  • 15
  • 12
  • This worked great for me! At first I thought the instruction was to place this function in the actual appDelegate, but I was able to drop it in the view controller that started an image upload, that enabled me to have this alert display after they had dismissed that viewController. Thanks! – Daniel Patriarca May 11 '19 at 10:37
  • alert present two times – PvUIDev Jul 10 '20 at 13:06
0

For the easiness, I used category

UIAlertController+UIWindow.h

@interface UIAlertController (UIWindow)

- (void)show;
- (void)show:(BOOL)animated;

@end

UIAlertController+UIWindow.m

#import <objc/runtime.h>

@interface UIAlertController (Private)

@property (nonatomic, strong) UIWindow *alertWindow;

@end

@implementation UIAlertController (Private)

@dynamic alertWindow;

- (void)setAlertWindow:(UIWindow *)alertWindow {
    objc_setAssociatedObject(self, @selector(alertWindow), alertWindow, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIWindow *)alertWindow {
    return objc_getAssociatedObject(self, @selector(alertWindow));
}

@end

@implementation UIAlertController (UIWindow)

- (void)show {
    [self show:YES];
}

- (void)show:(BOOL)animated {
    [self setupWindow];
    [self.alertWindow.rootViewController presentViewController:self animated:animated completion:nil];
}

- (void)setupWindow {
    self.alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.alertWindow.rootViewController = [[UIViewController alloc] init];

    id<UIApplicationDelegate> delegate = [UIApplication sharedApplication].delegate;
    if ([delegate respondsToSelector:@selector(window)]) {
        self.alertWindow.tintColor = delegate.window.tintColor;
    }

    UIWindow *topWindow = [UIApplication sharedApplication].windows.lastObject;
    self.alertWindow.windowLevel = topWindow.windowLevel + 1;

    [self.alertWindow makeKeyAndVisible];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];

    // precaution to insure window gets destroyed
    self.alertWindow.hidden = YES;
    self.alertWindow = nil;
}

Use:

UIAlertController *alertController;
// -- code --
[alertController show];
Lal Krishna
  • 15,485
  • 6
  • 64
  • 84
0

I have written a static class to make code reusable in swift 4, This class provied different methods for showing alerts.

        class AlertUtility: NSObject {

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

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

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

            // Check whether it's conditional or not ('YES' 'NO, or just 'OK')
            if isConditional
            {
                alert.addAction(UIAlertAction(title: NSLocalizedString("yes", comment: ""), style: UIAlertActionStyle.default, handler: { (action: UIAlertAction) in
                    alert.dismiss(animated: true, completion: nil)
                    completionBlock(true)
                }))

                alert.addAction(UIAlertAction(title: NSLocalizedString("no", comment: ""), style: UIAlertActionStyle.default, handler: { (action: UIAlertAction) in
                    alert.dismiss(animated: true, completion: nil)
                    completionBlock(false)
                }))
            }
            else
            {
                alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "ok"), style: UIAlertActionStyle.default, handler: { (action: UIAlertAction) in
                    alert.dismiss(animated: true, completion: nil)
                    completionBlock(true)
                }))
            }

            viewController.present(alert, animated: true, completion: nil)
        }

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

        }

        static func showComingSoon(viewController: UIViewController) {
            let alert = UIAlertController(title: "", message: "Coming Soon", preferredStyle: .alert)

            viewController.present(alert, animated: true, completion: {})

            // change to desired number of seconds (in this case 1 seconds)
            let when = DispatchTime.now() + 0.6
            DispatchQueue.main.asyncAfter(deadline: when){
                // your code with delay
                alert.dismiss(animated: true, completion: nil)
            }
        }

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

            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: { (_) 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)
       }

    }
Pramod More
  • 1,220
  • 2
  • 22
  • 51
-1

iOS 13 and Above

Since iOS 13 and above the SceneDelegate was introduced to take care of UI related life cycles. Now, Every instances of Apps UI is represented by UIScene. Hence, the UIWindow is no longer available in Appdelegate.

How can we show alert when a remote notification received?

This way we can achieve that.

guard let rootViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController else { return }
let alert = UIAlertController(title: "Alert", message: "Message", preferredStyle: UIAlertController.Style.alert)
            alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
DispatchQueue.main.async {
      rootViewController.present(alert, animated: true, completion: {
                    
      })
}
Quest
  • 1
  • 1
  • This code doesn't take into account an app with multiple scenes (such as with iPad multitasking). This code also fails if the root view controller of the randomly selected scene happens to be presenting another view controller already (which is the whole point of the question). – HangarRash Apr 25 '23 at 03:45