Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to present UIAlertController when not in a view controller?

Scenario: The user taps on a button on a view controller. The view controller is the topmost (obviously) in the navigation stack. The tap invokes a utility class method called on another class. A bad thing happens there and I want to display an alert right there before control returns to the view controller.

+ (void)myUtilityMethod {     // do stuff     // something bad happened, display an alert. } 

This was possible with UIAlertView (but perhaps not quite proper).

In this case, how do you present a UIAlertController, right there in myUtilityMethod?

like image 456
Murray Sagal Avatar asked Oct 24 '14 19:10

Murray Sagal


People also ask

How do I present a view controller from another view controller?

Start a segue from any object that implements an action method, such as a control or gesture recognizer. You may also start segues from table rows and collection view cells. Right-click the control or object in your current view controller. Drag the cursor to the view controller you want to present.

How do I add a view controller in storyboard?

To create a new view controller, select File->New->File and select a Cocoa Touch Class. Choose whether to create it with Swift or Objective-C and inherit from UIViewController . Don't create it with a xib (a separate Interface Builder file), as you will most likely add it to an existing storyboard.


1 Answers

At WWDC, I stopped in at one of the labs and asked an Apple Engineer this same question: "What was the best practice for displaying a UIAlertController?" And he said they had been getting this question a lot and we joked that they should have had a session on it. He said that internally Apple is creating a UIWindow with a transparent UIViewController and then presenting the UIAlertController on it. Basically what is in Dylan Betterman's answer.

But I didn't want to use a subclass of UIAlertController because that would require me changing my code throughout my app. So with the help of an associated object, I made a category on UIAlertController that provides a show method in Objective-C.

Here is the relevant code:

#import "UIAlertController+Window.h" #import <objc/runtime.h>  @interface UIAlertController (Window)  - (void)show; - (void)show:(BOOL)animated;  @end  @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 (Window)  - (void)show {     [self show:YES]; }  - (void)show:(BOOL)animated {     self.alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];     self.alertWindow.rootViewController = [[UIViewController alloc] init];      id<UIApplicationDelegate> delegate = [UIApplication sharedApplication].delegate;     // Applications that does not load with UIMainStoryboardFile might not have a window property:     if ([delegate respondsToSelector:@selector(window)]) {         // we inherit the main window's tintColor         self.alertWindow.tintColor = delegate.window.tintColor;     }      // window level is above the top window (this makes the alert, if it's a sheet, show over the keyboard)     UIWindow *topWindow = [UIApplication sharedApplication].windows.lastObject;     self.alertWindow.windowLevel = topWindow.windowLevel + 1;      [self.alertWindow makeKeyAndVisible];     [self.alertWindow.rootViewController presentViewController:self animated:animated completion:nil]; }  - (void)viewDidDisappear:(BOOL)animated {     [super viewDidDisappear:animated];          // precaution to ensure window gets destroyed     self.alertWindow.hidden = YES;     self.alertWindow = nil; }  @end 

Here is a sample usage:

// need local variable for TextField to prevent retain cycle of Alert otherwise UIWindow // would not disappear after the Alert was dismissed __block UITextField *localTextField; UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Global Alert" message:@"Enter some text" preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {     NSLog(@"do something with text:%@", localTextField.text); // do NOT use alert.textfields or otherwise reference the alert in the block. Will cause retain cycle }]]; [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {     localTextField = textField; }]; [alert show]; 

The UIWindow that is created will be destroyed when the UIAlertController is dealloced, since it is the only object that is retaining the UIWindow. But if you assign the UIAlertController to a property or cause its retain count to increase by accessing the alert in one of the action blocks, the UIWindow will stay on screen, locking up your UI. See the sample usage code above to avoid in the case of needing to access UITextField.

I made a GitHub repo with a test project: FFGlobalAlertController

like image 199
agilityvision Avatar answered Oct 08 '22 18:10

agilityvision