38

In obj-C when another iOS app (mail attachment, web link) was tapped with a file or link associated with my app. I would then catch this on openURL or didFinishLaunchingWithOptions and show a UIAlertView to confirm the user wants to import the data. Now that UIAlertView is depreciated I am trying to do the same thing but not really sure about the best way to do this?

I am having trouble showing a simple alert when my App receives data from another app. This code worked fine in Objective-C with a UIAlertView:

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
    if (url)
    {
        self.URLString = [url absoluteString];
        NSString *message = @"Received a data exchange request. Would you like to import it?";
        importAlert = [[UIAlertView alloc] initWithTitle:@"Data Received" message:message delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
        [importAlert show];
    }

    return YES;
}

But when I try and switch to UIAlertViewController and Swift I can't seem to find a simple way to display the message:

func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject?) -> Bool {
    let URLString: String = url.absoluteString!
    let message: String = "Received data. Would you like to import it?"

    var importAlert: UIAlertController = UIAlertController(title: "Data Received", message: message, preferredStyle: UIAlertControllerStyle.Alert)
    importAlert.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
    importAlert.addAction(UIAlertAction(title: "Ok", style: .Default, handler:
    { action in
        switch action.style {
        case .Default:
            println("default")  
        case .Cancel:
            println("cancel")   
        case .Destructive:
            println("destructive")
        }
    }))

    self.presentViewController(importAlert, animated: true, completion: nil)
    return true
}

I get a compile time error that AppDelegate does not have a member named presentViewController

I have seen some convoluted methods to get the AppDelegate to display an UIAlertViewController on StackOverflow but I was hoping there was something a little simpler.

All I really need to do is show the user a quick message that they got some data and have them decide what they want to do with it. Once were done my app will continue to open and come to foreground (similar code in didFinishLaunchingWithOptions for cold start) with either the new data added or not based on the alert selection.

I could flag a global variable that I check in all my viewWillAppear func but this would be a lot of duplication since I have 30+ views.

Let me know if you have any ideas.

Thanks

Greg

pableiros
  • 14,932
  • 12
  • 99
  • 105
Greg Robertson
  • 2,317
  • 1
  • 16
  • 30
  • presentViewController is a method associated with UIViewController and hence you cannot run this method in the app delegate. – ZeMoon Oct 29 '14 at 10:19
  • What you are describing will require notifications. You cannot show any alert to the user outside of the application. – ZeMoon Oct 29 '14 at 10:20
  • You will also notice that the app has launched by the time the openUrl delegate method is called. – ZeMoon Oct 29 '14 at 10:24
  • In obj-C when another iOS app (mail attachment, web link) was tapped with a file or link associated with my app. I would then catch this on openURL or didFinishLaunchingWithOptions and show a UIAlertView to confirm the user wants to import the data. Now that UIAlertView is depreciated I am trying to do the same thing but not really sure about the best way to do this? Any ideas? – Greg Robertson Oct 29 '14 at 11:19
  • Updated question with intro to clarify it – Greg Robertson Oct 29 '14 at 11:25
  • Try using self.window.rootViewController.presentViewController(importAlert, animated: true, completion: nil) – ZeMoon Oct 29 '14 at 11:41

5 Answers5

91

Try using

self.window?.rootViewController?.presentViewController(importAlert, animated: true, completion: nil)

All you need is a viewController object to present the AlertController from.

In Swift 4:

self.window?.rootViewController?.present(importAlert, animated: true, completion: nil)
Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
ZeMoon
  • 20,054
  • 5
  • 57
  • 98
  • What a great answer, wish I had thought of it. ;-) – Daniel T. Oct 29 '14 at 11:44
  • Thank you, works exactly like I need it to, only mod is: self.window?.rootViewController?.presentViewController(importAlert, animated: true, completion: nil) – Greg Robertson Oct 30 '14 at 01:44
  • 2
    Nice. If you are doing it in ObjC the code is -- [self.window.rootViewController presentViewController:alert animated:true completion:nil]; – JScarry Nov 10 '14 at 19:41
  • How do you then dismiss the alert? I tried this var alert2 = UIAlertController(title: "", message: "", preferredStyle: UIAlertControllerStyle.Alert) alert2.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { action in self.window?.rootViewController?.dismissViewControllerAnimated(true, completion: nil) })) self.window?.rootViewController?.presentViewController(alert2, animated: true, completion: nil) But got the error "Could not find member 'Default'" – TimWhiting Feb 20 '15 at 12:39
  • The suggested solution works only when you don't have data restoration enabled. With data restoration active the app will jump right to the place user left it and the message won't appear, the following will be logged: "Warning: Attempt to present on whose view is not in the window hierarchy!". See Marie Amida's answer below on how to handle such a case. – Vitalii Sep 20 '16 at 14:26
  • ... also I found cases when self.window?.rootViewController? might be nil. I've corrected Marie's answer below to have more universal approach. – Vitalii Sep 21 '16 at 14:44
20

Use this code to launch alertCV from appdelegate

    dispatch_async(dispatch_get_main_queue(), {
          let importantAlert: UIAlertController = UIAlertController(title: "Action Sheet", message: "Hello I was presented from appdelegate ;)", preferredStyle: .ActionSheet)
        self.window?.rootViewController?.presentViewController(importantAlert, animated: true, completion: nil)
    })

Hope this helps!

Aditya Gaonkar
  • 660
  • 6
  • 13
  • 3
    best answer here, imho. Because of: warning : `Attempt to present on whose view is not in the window hierarchy!`. => Too fast, so `dispatch_async(dispatch_get_main_queue() ...` help us to be in the right time in the right place! – Dima Deplov Oct 08 '15 at 00:32
  • In my case, I was not receiving any message about window hierarchy... but my viewController insisted in not appear. Thanks, guys. – Thomás Pereira Dec 22 '15 at 11:17
  • This method works. However, just having `self.window?.rootViewController?.presentViewController(importAlert, animated: true, completion: nil)` in `didFinishLaunchingWithOptions` method did not work for me probably because the view controller was not created by the time it was called? – Maduranga E Jul 03 '16 at 07:23
  • How can i do this in objective c? – Radames E. Hernandez Dec 23 '16 at 18:12
2

The best way I've found is the following:

        let importantAlert: UIAlertController = UIAlertController(title: "Action Sheet", message: "Hello I was presented from appdelegate ;)", preferredStyle: .ActionSheet) //.Alert .ActionSheet
        var hostVC = UIApplication.sharedApplication().keyWindow?.rootViewController
        while let next = hostVC?.presentedViewController {
            hostVC = next
        }
        hostVC?.presentViewController(importantAlert, animated: true, completion: nil)


        let delay = 1.5 * Double(NSEC_PER_SEC)
        let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
        dispatch_after(time, dispatch_get_main_queue()) {

            importantAlert.dismissViewControllerAnimated(true, completion: nil)

            return
        }

I've added a timer so the UIAlertController is dismissed since it doesn't have any buttons in it.

Thanks to: https://stackoverflow.com/a/33128884/6144027 Brilliant answer on how to present a UIAlertController from AppDelegate.

Community
  • 1
  • 1
Marie Amida
  • 556
  • 1
  • 5
  • 14
1

From inside the app delegate.

window.rootViweController.presentViewController...

Daniel T.
  • 32,821
  • 6
  • 50
  • 72
0

The accepted answer in Swift 3 in case it helps anyone:

self.window?.rootViewController?.present(importAlert, animated: true, completion: nil)
user1333394
  • 696
  • 6
  • 6