1

I am updating some UIAlertViews, deprecated since iOS 9.0 to UIAlertViewControllers.

With UIAlertView, it was possible to just throw an alert from any code being executed--even in a utility class or shared instance--with the simple line:

[alertView show];

So if I call a shared instance such as

- (void)didTapDeleteButton:(id)sender {
    NSArray *itemsToDelete = [self.selectedIndexPathToContact allValues];

    [[IDModel sharedInstance] deleteItems:itemsToDelete];

//which contains the code:

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Keep Archive Copy?"
                                                    message:nil
                                                   delegate:self
                                          cancelButtonTitle:@"No"
                                          otherButtonTitles:@"OK",nil];
alertInvite.alertViewStyle = UIAlertViewStyleDefault;
alertInvite.tag=100;
[alertView show];

everything worked fine.

However, with the UIAlertController, this is not allowed. If you put the following code in the method of a class accessible via shared instance, when you get to presentViewController, it throws an error:

UIAlertController *alertView = [UIAlertController alertControllerWithTitle:@"Delete Item?" message:nil preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction* yesButton = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
    [alertView dismissViewControllerAnimated:YES completion:nil];
}];

UIAlertAction* noButton = [UIAlertAction actionWithTitle:@"Not Now" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
    [alertView dismissViewControllerAnimated:YES completion:nil];
}];

[alertView addAction:noButton];
[alertView addAction:yesButton];
if ([alertView respondsToSelector:@selector(setPreferredAction:)]) {
    [alertView setPreferredAction:yesButton];
}
//FOLLOWING THROWS ERROR
[self presentViewController:alertView animated:YES completion:nil];

on the last line, that the class (reached via a shared instance) does not have this method. It seems you must use a more complicated way to throw alert. I've seen some somwehat convoluted approaches such as the following:

id rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
if([rootViewController isKindOfClass:[UINavigationController class]])
{
    rootViewController = ((UINavigationController *)rootViewController).viewControllers.firstObject;
}
if([rootViewController isKindOfClass:[UITabBarController class]])
{
    rootViewController = ((UITabBarController *)rootViewController).selectedViewController;
}
[rootViewController presentViewController:alertInvite animated:YES completion:nil];

However, this does not work for me as I don't think my shared instance has a rootviewcontroller. Can anyone suggest a simple, straightforward way to do this?

user6631314
  • 1,751
  • 1
  • 13
  • 44

3 Answers3

1

to show an alert from any code I could think of doing:

UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Alert" message:@"message" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction: [UIAlertAction actionWithTitle:@"Yes" style:UIAlertActionStyleDefault handler:nil]];
alert.TitleColor = [UIColor whiteColor];

id<UIApplicationDelegate> delegate = [UIApplication sharedApplication].delegate;
UIViewController *vc = delegate.window.rootViewController;
[vc presentViewController:alert animated:YES completion:nil];

Note:

Note that in most cases I would NOT do this.
Non ui code shouldn't do ui! Which is likely also part of the reason why apple made the change: It encourages a proper model||view separation

Community
  • 1
  • 1
Daij-Djan
  • 49,552
  • 17
  • 113
  • 135
  • Would you send a notification to the View Controller. Or how would you tell the onscreen VC to throw the alert? – user6631314 Aug 22 '18 at 16:12
  • 1
    You can't just assume you can show the alert from the app's rootViewController. That could already be presenting a view controller. – rmaddy Aug 22 '18 at 16:18
  • you cant assume for a call to self either ;) yet the sample does too *hehe* I grant you it might not be the right thing in all cases, and I thought my NOTE paragraph implied i didnt agree – Daij-Djan Aug 22 '18 at 17:16
  • the proper way is NOT to do it at all bbut keep the UI in the View&Controller not the model – Daij-Djan Aug 22 '18 at 17:17
  • That's what I'm asking. If you put the alert code in the view controller, how should the model tell it to fire the alert? A notification or can you suggest another way? The model is handling the interaction with core data. – user6631314 Aug 22 '18 at 17:51
  • 1
    Is use completion blocks or delegate protocols above notifications :) I’ll generally prefer this cause notifications can be hard to reason about – Daij-Djan Aug 22 '18 at 18:10
1

I created an extension on UIViewController that allows me to create a new window and present a view controller from there. This allows me to present from any class, not just a view controller. Also, it prevents issues where you try to display an alert view from a view controller that is already presenting a view controller.

extension UIViewController {
    func presentFromNewWindow(animated: Bool = true, completion: (() -> Void)? = nil) {
        let window = newWindow()

        if let rootViewController = window.rootViewController {
            window.makeKeyAndVisible()
            rootViewController.present(self, animated: animated, completion: completion)
        }
    }

    private func newWindow() -> UIWindow {
        let window = UIWindow(frame: UIScreen.main.bounds)
        let rootViewController = UIViewController()
        rootViewController.view.backgroundColor = .clear
        window.backgroundColor = .clear
        window.rootViewController = rootViewController
        window.windowLevel = UIWindowLevelAlert

        return window
    }
}

You can then use this method to present your Alert Controller (or any UIViewController):

alertViewController.presentFromNewWindow()

When you dismiss the alert view controller, it is removed from the view controller and the window is removed from the hierarchy.

Sean Kladek
  • 4,396
  • 1
  • 23
  • 29
0

Your question is quite vague, I believe. But I think what you've been looking for is an example of presenting a UIAlertController. Is that correct? If so, continue reading...

Example:

UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Alert" message:@"message" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction: [UIAlertAction actionWithTitle:@"Yes" style:UIAlertActionStyleDefault handler:nil]];
alert.TitleColor = [UIColor whiteColor];
[self presentViewController:alert animated:YES completion:nil];

doc: https://developer.apple.com/documentation/uikit/uialertcontroller

Glenn Posadas
  • 12,555
  • 6
  • 54
  • 95