uialert objective example ios uialertcontroller

ios - objective - uialertcontroller swift 4



¿Cómo presentar UIAlertController cuando no está en un controlador de vista? (30)

Además de las excelentes respuestas dadas ( agilityvision , adib , malhal ). Para alcanzar el comportamiento de la cola como en los viejos UIAlertViews (evitar la superposición de ventanas de alerta), use este bloque para observar la disponibilidad a nivel de ventana:

@interface UIWindow (WLWindowLevel) + (void)notifyWindowLevelIsAvailable:(UIWindowLevel)level withBlock:(void (^)())block; @end @implementation UIWindow (WLWindowLevel) + (void)notifyWindowLevelIsAvailable:(UIWindowLevel)level withBlock:(void (^)())block { UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; if (keyWindow.windowLevel == level) { // window level is occupied, listen for windows to hide id observer; observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIWindowDidBecomeHiddenNotification object:keyWindow queue:nil usingBlock:^(NSNotification *note) { [[NSNotificationCenter defaultCenter] removeObserver:observer]; [self notifyWindowLevelIsAvailable:level withBlock:block]; // recursive retry }]; } else { block(); // window level is available } } @end

Ejemplo completo:

[UIWindow notifyWindowLevelIsAvailable:UIWindowLevelAlert withBlock:^{ UIWindow *alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; alertWindow.windowLevel = UIWindowLevelAlert; alertWindow.rootViewController = [UIViewController new]; [alertWindow makeKeyAndVisible]; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:nil preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { alertWindow.hidden = YES; }]]; [alertWindow.rootViewController presentViewController:alertController animated:YES completion:nil]; }];

Esto le permitirá evitar la superposición de ventanas de alerta. Se puede usar el mismo método para separar y poner en cola los controladores de vista para cualquier número de capas de ventana.

Escenario: el usuario toca un botón en un controlador de vista. El controlador de vista es el más alto (obviamente) en la pila de navegación. El toque invoca un método de clase de utilidad llamado en otra clase. Algo malo sucede allí y quiero mostrar una alerta justo antes de que el control regrese al controlador de vista.

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

Esto fue posible con UIAlertView (pero quizás no del todo correcto).

En este caso, ¿cómo presentas un UIAlertController , justo en myUtilityMethod ?


Hay 2 enfoques que puede usar:

-Utilice UIAlertView o ''UIActionSheet'' en su lugar (no recomendado, porque está en desuso en iOS 8 pero funciona ahora)

-De alguna manera recuerde el último controlador de vista que se presenta. Aquí hay un ejemplo.

@interface UIViewController (TopController) + (UIViewController *)topViewController; @end // implementation #import "UIViewController+TopController.h" #import <objc/runtime.h> static __weak UIViewController *_topViewController = nil; @implementation UIViewController (TopController) + (UIViewController *)topViewController { UIViewController *vc = _topViewController; while (vc.parentViewController) { vc = vc.parentViewController; } return vc; } + (void)load { [super load]; [self swizzleSelector:@selector(viewDidAppear:) withSelector:@selector(myViewDidAppear:)]; [self swizzleSelector:@selector(viewWillDisappear:) withSelector:@selector(myViewWillDisappear:)]; } - (void)myViewDidAppear:(BOOL)animated { if (_topViewController == nil) { _topViewController = self; } [self myViewDidAppear:animated]; } - (void)myViewWillDisappear:(BOOL)animated { if (_topViewController == self) { _topViewController = nil; } [self myViewWillDisappear:animated]; } + (void)swizzleSelector:(SEL)sel1 withSelector:(SEL)sel2 { Class class = [self class]; Method originalMethod = class_getInstanceMethod(class, sel1); Method swizzledMethod = class_getInstanceMethod(class, sel2); BOOL didAddMethod = class_addMethod(class, sel1, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, sel2, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } @end

Uso:

[[UIViewController topViewController] presentViewController:alertController ...];


Intenté todo lo mencionado, pero sin éxito. El método que utilicé para Swift 3.0:

extension UIAlertController { func show() { present(animated: true, completion: nil) } func present(animated: Bool, completion: (() -> Void)?) { if var topController = UIApplication.shared.keyWindow?.rootViewController { while let presentedViewController = topController.presentedViewController { topController = presentedViewController } topController.present(self, animated: animated, completion: completion) } } }


Kevin Sliech proporcionó una gran solución.

Ahora uso el siguiente código en mi subclase principal UIViewController.

Una pequeña alteración que hice fue verificar si el mejor controlador de presentación no es un UIViewController simple. Si no, tiene que ser un VC que presente un VC simple. Por lo tanto, devolvemos el VC que se presenta en su lugar.

- (UIViewController *)bestPresentationController { UIViewController *bestPresentationController = [UIApplication sharedApplication].keyWindow.rootViewController; if (![bestPresentationController isMemberOfClass:[UIViewController class]]) { bestPresentationController = bestPresentationController.presentedViewController; } return bestPresentationController; }

Parece que todo ha funcionado hasta ahora en mis pruebas.

Gracias Kevin!


La respuesta de @ agilityvision es muy buena. Tengo sentido en proyectos rápidos, así que pensé en compartir mi opinión sobre su respuesta usando swift 3.0

fileprivate class MyUIAlertController: UIAlertController { typealias Handler = () -> Void struct AssociatedKeys { static var alertWindowKey = "alertWindowKey" } dynamic var _alertWindow: UIWindow? var alertWindow: UIWindow? { return objc_getAssociatedObject(self, &AssociatedKeys.alertWindowKey) as? UIWindow } func setAlert(inWindow window: UIWindow) { objc_setAssociatedObject(self, &AssociatedKeys.alertWindowKey, _alertWindow, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } func show(completion: Handler? = nil) { show(animated: true, completion: completion) } func show(animated: Bool, completion: Handler? = nil) { _alertWindow = UIWindow(frame: UIScreen.main.bounds) _alertWindow?.rootViewController = UIViewController() if let delegate: UIApplicationDelegate = UIApplication.shared.delegate, let window = delegate.window { _alertWindow?.tintColor = window?.tintColor } let topWindow = UIApplication.shared.windows.last _alertWindow?.windowLevel = topWindow?.windowLevel ?? 0 + 1 _alertWindow?.makeKeyAndVisible() _alertWindow?.rootViewController?.present(self, animated: animated, completion: completion) } fileprivate override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) _alertWindow?.isHidden = true _alertWindow = nil } }


Para iOS 13, basándose en las respuestas de mythicalcoder y bobbyrehm :

En iOS 13, si está creando su propia ventana para presentar la alerta, debe mantener una referencia fuerte a esa ventana o su alerta no se mostrará porque la ventana se desasignará inmediatamente cuando su referencia salga del alcance.

Además, deberá establecer la referencia en nulo nuevamente después de que se descarte la alerta para eliminar la ventana para continuar permitiendo la interacción del usuario en la ventana principal debajo de ella.

Puede crear una UIViewController subclase para encapsular la lógica de administración de memoria de la ventana:

class WindowAlertPresentationController: UIViewController { // MARK: - Properties private lazy var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds) private let alert: UIAlertController // MARK: - Initialization init(alert: UIAlertController) { self.alert = alert super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("This initializer is not supported") } // MARK: - Presentation func present(animated: Bool, completion: (() -> Void)?) { window?.rootViewController = self window?.windowLevel = UIWindow.Level.alert + 1 window?.makeKeyAndVisible() present(alert, animated: animated, completion: completion) } // MARK: - Overrides override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { super.dismiss(animated: flag) { self.window = nil completion?() } } }

Puede usar esto como está, o si desea un método conveniente en su UIAlertController , puede incluirlo en una extensión:

extension UIAlertController { func presentInOwnWindow(animated: Bool, completion: (() -> Void)?) { let windowAlertPresentationController = WindowAlertPresentationController(alert: self) windowAlertPresentationController.present(animated: animated, completion: completion) } }


Parece funcionar:

static UIViewController *viewControllerForView(UIView *view) { UIResponder *responder = view; do { responder = [responder nextResponder]; } while (responder && ![responder isKindOfClass:[UIViewController class]]); return (UIViewController *)responder; } -(void)showActionSheet { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; [alertController addAction:[UIAlertAction actionWithTitle:@"Do it" style:UIAlertActionStyleDefault handler:nil]]; [viewControllerForView(self) presentViewController:alertController animated:YES completion:nil]; }


Puede enviar la vista o controlador actual como parámetro:

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


Puede intentar implementar una categoría UIViewController con mehtod como - (void)presentErrorMessage; Y y dentro de ese método implementa UIAlertController y luego presentarlo self . Que en su código de cliente tendrá algo como:

[myViewController presentErrorMessage];

De esta forma evitará parámetros innecesarios y advertencias sobre que la vista no está en la jerarquía de ventanas.


Si alguien está interesado, creé una versión Swift 3 de @agilityvision answer. El código:

import Foundation import UIKit extension UIAlertController { var window: UIWindow? { get { return objc_getAssociatedObject(self, "window") as? UIWindow } set { objc_setAssociatedObject(self, "window", newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } open override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.window?.isHidden = true self.window = nil } func show(animated: Bool = true) { let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UIViewController(nibName: nil, bundle: nil) let delegate = UIApplication.shared.delegate if delegate?.window != nil { window.tintColor = delegate!.window!!.tintColor } window.windowLevel = UIApplication.shared.windows.last!.windowLevel + 1 window.makeKeyAndVisible() window.rootViewController!.present(self, animated: animated, completion: nil) self.window = window } }


Uso este código con algunas pequeñas variaciones personales en mi clase AppDelegate

-(UIViewController*)presentingRootViewController { UIViewController *vc = self.window.rootViewController; if ([vc isKindOfClass:[UINavigationController class]] || [vc isKindOfClass:[UITabBarController class]]) { // filter nav controller vc = [AppDelegate findChildThatIsNotNavController:vc]; // filter tab controller if ([vc isKindOfClass:[UITabBarController class]]) { UITabBarController *tbc = ((UITabBarController*)vc); if ([tbc viewControllers].count > 0) { vc = [tbc viewControllers][tbc.selectedIndex]; // filter nav controller again vc = [AppDelegate findChildThatIsNotNavController:vc]; } } } return vc; } /** * Private helper */ +(UIViewController*)findChildThatIsNotNavController:(UIViewController*)vc { if ([vc isKindOfClass:[UINavigationController class]]) { if (((UINavigationController *)vc).viewControllers.count > 0) { vc = [((UINavigationController *)vc).viewControllers objectAtIndex:0]; } } return vc; }


crear clase auxiliar AlertWindow y luego usar como

let alertWindow = AlertWindow(); let alert = UIAlertController(title: "Hello", message: "message", preferredStyle: .alert); let cancel = UIAlertAction(title: "Ok", style: .cancel){(action) in //.... action code here // reference to alertWindow retain it. Every action must have this at end alertWindow.isHidden = true; // here AlertWindow.deinit{ } } alert.addAction(cancel); alertWindow.present(alert, animated: true, completion: nil) class AlertWindow:UIWindow{ convenience init(){ self.init(frame:UIScreen.main.bounds); } override init(frame: CGRect) { super.init(frame: frame); if let color = UIApplication.shared.delegate?.window??.tintColor { tintColor = color; } rootViewController = UIViewController() windowLevel = UIWindowLevelAlert + 1; makeKeyAndVisible() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit{ // semaphor.signal(); } func present(_ ctrl:UIViewController, animated:Bool, completion: (()->Void)?){ rootViewController!.present(ctrl, animated: animated, completion: completion); } }


Agregando a la respuesta de Zev (y volviendo a Objective-C), podría encontrarse con una situación en la que su controlador de vista raíz presenta algún otro VC a través de una segue u otra cosa. Llamar a presentViewController en el VC raíz se encargará de esto:

[[UIApplication sharedApplication].keyWindow.rootViewController.presentedViewController presentViewController:alertController animated:YES completion:^{}];

Esto solucionó un problema que tenía cuando el VC raíz se había segmentado a otro VC, y en lugar de presentar el controlador de alerta, se emitió una advertencia como las que se informaron anteriormente:

Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!

No lo he probado, pero esto también puede ser necesario si su VC raíz es un controlador de navegación.


Aquí está la respuesta de mythicalcoder como una extensión, probada y trabajando en Swift 4:

extension UIAlertController { func presentInOwnWindow(animated: Bool, completion: (() -> Void)?) { let alertWindow = UIWindow(frame: UIScreen.main.bounds) alertWindow.rootViewController = UIViewController() alertWindow.windowLevel = UIWindowLevelAlert + 1; alertWindow.makeKeyAndVisible() alertWindow.rootViewController?.present(self, animated: animated, completion: completion) } }

Ejemplo de uso:

let alertController = UIAlertController(title: "<Alert Title>", message: "<Alert Message>", preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil)) alertController.presentInOwnWindow(animated: true, completion: { print("completed") })


Crear extensión como en la respuesta de Aviel Gross. Aquí tienes la extensión Objective-C.

Aquí tienes el archivo de encabezado * .h

// UIAlertController+Showable.h #import <UIKit/UIKit.h> @interface UIAlertController (Showable) - (void)show; - (void)presentAnimated:(BOOL)animated completion:(void (^)(void))completion; - (void)presentFromController:(UIViewController *)viewController animated:(BOOL)animated completion:(void (^)(void))completion; @end

E implementación: * .m

// UIAlertController+Showable.m #import "UIAlertController+Showable.h" @implementation UIAlertController (Showable) - (void)show { [self presentAnimated:YES completion:nil]; } - (void)presentAnimated:(BOOL)animated completion:(void (^)(void))completion { UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController; if (rootVC != nil) { [self presentFromController:rootVC animated:animated completion:completion]; } } - (void)presentFromController:(UIViewController *)viewController animated:(BOOL)animated completion:(void (^)(void))completion { if ([viewController isKindOfClass:[UINavigationController class]]) { UIViewController *visibleVC = ((UINavigationController *)viewController).visibleViewController; [self presentFromController:visibleVC animated:animated completion:completion]; } else if ([viewController isKindOfClass:[UITabBarController class]]) { UIViewController *selectedVC = ((UITabBarController *)viewController).selectedViewController; [self presentFromController:selectedVC animated:animated completion:completion]; } else { [viewController presentViewController:self animated:animated completion:completion]; } } @end

Está utilizando esta extensión en su archivo de implementación de esta manera:

#import "UIAlertController+Showable.h" UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Title here" message:@"Detail message here" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}]; [alert addAction:defaultAction]; // Add more actions if needed [alert show];


Cruce publicar mi answer ya que estos dos hilos no están marcados como engañados ...

Ahora que UIViewController es parte de la cadena de respuesta, puede hacer algo como esto:

if let vc = self.nextResponder()?.targetForAction(#selector(UIViewController.presentViewController(_:animated:completion:)), withSender: self) as? UIViewController { let alert = UIAlertController(title: "A snappy title", message: "Something bad happened", preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil)) vc.presentViewController(alert, animated: true, completion: nil) }


En WWDC, me detuve en uno de los laboratorios y le hice a un ingeniero de Apple la misma pregunta: "¿Cuál fue la mejor práctica para mostrar un UIAlertController ?" Y él dijo que habían recibido muchas veces esta pregunta y bromeamos diciendo que deberían haber tenido una sesión al respecto. Dijo que internamente Apple está creando una UIWindow con un UIViewController transparente y luego presenta el UIAlertController en él. Básicamente lo que está en la respuesta de Dylan Betterman.

Pero no quería usar una subclase de UIAlertController porque eso requeriría cambiar mi código en toda mi aplicación. Entonces, con la ayuda de un objeto asociado, hice una categoría en UIAlertController que proporciona un método show en Objective-C.

Aquí está el código relevante:

#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

Aquí hay un ejemplo de uso:

// 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];

La UIWindow que se crea se destruirá cuando se UIAlertController UIAlertController, ya que es el único objeto que retiene la UIWindow . Pero si asigna el UIAlertController a una propiedad o hace que su recuento de retención aumente al acceder a la alerta en uno de los bloques de acción, la UIWindow permanecerá en la pantalla, bloqueando su UI. Vea el código de uso de muestra anterior para evitar en caso de necesitar acceder a UITextField .

Hice un repositorio de GitHub con un proyecto de prueba: FFGlobalAlertController


Esto funciona en Swift para controladores de vista normales e incluso si hay un controlador de navegación en la pantalla:

let alert = UIAlertController(...) let alertWindow = UIWindow(frame: UIScreen.main.bounds) alertWindow.rootViewController = UIViewController() alertWindow.windowLevel = UIWindowLevelAlert + 1; alertWindow.makeKeyAndVisible() alertWindow.rootViewController?.presentViewController(alert, animated: true, completion: nil)


Forma abreviada de presentar la alerta en Objective-C:

[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:alertController animated:YES completion:nil];

Donde alertController es su objeto UIAlertController .

NOTA: También deberá asegurarse de que su clase auxiliar amplíe UIViewController


La respuesta de @ agilityvision se tradujo a Swift4 / iOS11. No he usado cadenas localizadas, pero puedes cambiar eso fácilmente:

import UIKit /** An alert controller that can be called without a view controller. Creates a blank view controller and presents itself over that **/ class AlertPlusViewController: UIAlertController { private var alertWindow: UIWindow? override func viewDidLoad() { super.viewDidLoad() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.alertWindow?.isHidden = true alertWindow = nil } func show() { self.showAnimated(animated: true) } func showAnimated(animated _: Bool) { let blankViewController = UIViewController() blankViewController.view.backgroundColor = UIColor.clear let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = blankViewController window.backgroundColor = UIColor.clear window.windowLevel = UIWindowLevelAlert + 1 window.makeKeyAndVisible() self.alertWindow = window blankViewController.present(self, animated: true, completion: nil) } func presentOkayAlertWithTitle(title: String?, message: String?) { let alertController = AlertPlusViewController(title: title, message: message, preferredStyle: .alert) let okayAction = UIAlertAction(title: "Ok", style: .default, handler: nil) alertController.addAction(okayAction) alertController.show() } func presentOkayAlertWithError(error: NSError?) { let title = "Error" let message = error?.localizedDescription presentOkayAlertWithTitle(title: title, message: message) } }


La respuesta de Zev Eisenberg es simple y directa, pero no siempre funciona, y puede fallar con este mensaje de advertencia:

Warning: Attempt to present <UIAlertController: 0x7fe6fd951e10> on <ThisViewController: 0x7fe6fb409480> which is already presenting <AnotherViewController: 0x7fe6fd109c00>

Esto se debe a que el rootViewController de Windows no está en la parte superior de las vistas presentadas. Para corregir esto, necesitamos avanzar por la cadena de presentación, como se muestra en mi código de extensión UIAlertController escrito en Swift 3:

/// show the alert in a view controller if specified; otherwise show from window''s root pree func show(inViewController: UIViewController?) { if let vc = inViewController { vc.present(self, animated: true, completion: nil) } else { // find the root, then walk up the chain var viewController = UIApplication.shared.keyWindow?.rootViewController var presentedVC = viewController?.presentedViewController while presentedVC != nil { viewController = presentedVC presentedVC = viewController?.presentedViewController } // now we present viewController?.present(self, animated: true, completion: nil) } } func show() { show(inViewController: nil) }

Actualizaciones el 15/09/2017:

Probado y confirmado que la lógica anterior todavía funciona muy bien en la semilla GM iOS 11 recién disponible. Sin embargo, el método más votado por agilityvision no lo hace: la vista de alerta presentada en una UIWindow recién UIWindow está debajo del teclado y potencialmente evita que el usuario toque sus botones. Esto se debe a que en iOS 11 todos los niveles de ventana superiores a los de la ventana del teclado se reducen a un nivel inferior.

keyWindow embargo, un artefacto de presentar desde keyWindow es la animación del teclado deslizándose hacia abajo cuando se presenta la alerta, y deslizándose hacia arriba nuevamente cuando se keyWindow la alerta. Si desea que el teclado permanezca allí durante la presentación, puede intentar presentar desde la ventana superior, como se muestra en el siguiente código:

func show(inViewController: UIViewController?) { if let vc = inViewController { vc.present(self, animated: true, completion: nil) } else { // get a "solid" window with the highest level let alertWindow = UIApplication.shared.windows.filter { $0.tintColor != nil || $0.className() == "UIRemoteKeyboardWindow" }.sorted(by: { (w1, w2) -> Bool in return w1.windowLevel < w2.windowLevel }).last // save the top window''s tint color let savedTintColor = alertWindow?.tintColor alertWindow?.tintColor = UIApplication.shared.keyWindow?.tintColor // walk up the presentation tree var viewController = alertWindow?.rootViewController while viewController?.presentedViewController != nil { viewController = viewController?.presentedViewController } viewController?.present(self, animated: true, completion: nil) // restore the top window''s tint color if let tintColor = savedTintColor { alertWindow?.tintColor = tintColor } } }

La única parte no tan buena del código anterior es que verifica el nombre de la clase UIRemoteKeyboardWindow para asegurarse de que podamos incluirlo también. Sin embargo, el código anterior funciona muy bien en iOS 9, 10 y 11 semillas GM, con el color de tinte correcto y sin los artefactos deslizantes del teclado.


La siguiente solución no funcionó aunque parecía bastante prometedora con todas las versiones. Esta solución está generando ADVERTENCIA .

Advertencia: ¡ Intente presentar en cuya vista no está en la jerarquía de la ventana!

https://.com/a/34487871/2369867 => Esto parece prometedor entonces. Pero no fue en Swift 3 . Entonces estoy respondiendo esto en Swift 3 y este no es un ejemplo de plantilla.

Este es un código bastante funcional en sí mismo una vez que pegue dentro de cualquier función.

Código autocontenido de Quick Swift 3

let alertController = UIAlertController(title: "<your title>", message: "<your message>", preferredStyle: UIAlertControllerStyle.alert) alertController.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.cancel, handler: nil)) let alertWindow = UIWindow(frame: UIScreen.main.bounds) alertWindow.rootViewController = UIViewController() alertWindow.windowLevel = UIWindowLevelAlert + 1; alertWindow.makeKeyAndVisible() alertWindow.rootViewController?.present(alertController, animated: true, completion: nil)

Esto está probado y funciona en Swift 3.


Para mejorar la respuesta de agilityvision , deberá crear una ventana con un controlador de vista raíz transparente y presentar la vista de alerta desde allí.

Sin embargo , siempre que tenga una acción en su controlador de alertas, no necesita mantener una referencia a la ventana . Como paso final del bloque del controlador de acciones, solo necesita ocultar la ventana como parte de la tarea de limpieza. Al tener una referencia a la ventana en el bloque del controlador, esto crea una referencia circular temporal que se rompería una vez que se descarta el controlador de alerta.

UIWindow* window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; window.rootViewController = [UIViewController new]; window.windowLevel = UIWindowLevelAlert + 1; UIAlertController* alertCtrl = [UIAlertController alertControllerWithTitle:... message:... preferredStyle:UIAlertControllerStyleAlert]; [alertCtrl addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK",@"Generic confirm") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { ... // do your stuff // very important to hide the window afterwards. // this also keeps a reference to the window until the action is invoked. window.hidden = YES; }]]; [window makeKeyAndVisible]; [window.rootViewController presentViewController:alertCtrl animated:YES completion:nil];


Publiqué una pregunta similar hace un par de meses y creo que finalmente he resuelto el problema. Sigue el enlace en la parte inferior de mi publicación si solo quieres ver el código.

La solución es usar una UIWindow adicional.

Cuando desee mostrar su UIAlertController:

  1. Haga que su ventana sea la clave y la ventana visible ( window.makeKeyAndVisible() )
  2. Simplemente use una instancia simple de UIViewController como rootViewController de la nueva ventana. ( window.rootViewController = UIViewController() )
  3. Presente su UIAlertController en el rootViewController de su ventana

Un par de cosas a tener en cuenta:

  • Su UIWindow debe estar fuertemente referenciada. Si no está fuertemente referenciado, nunca aparecerá (porque está liberado). Recomiendo usar una propiedad, pero también he tenido éxito con un objeto asociado .
  • Para garantizar que la ventana aparezca por encima de todo lo demás (incluido el sistema UIAlertControllers), configuro windowLevel. ( window.windowLevel = UIWindowLevelAlert + 1 )

Por último, tengo una implementación completa si solo quieres ver eso.

https://github.com/dbettermann/DBAlertController


Puede hacer lo siguiente con Swift 2.2:

let alertController: UIAlertController = ... UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)

Y Swift 3.0:

let alertController: UIAlertController = ... UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)


extension bastante genérica UIAlertController para todos los casos de UINavigationController y / o UITabBarController . También funciona si hay un VC modal en pantalla en este momento.

Uso:

//option 1: myAlertController.show() //option 2: myAlertController.present(animated: true) { //completion code... }

Esta es la extensión:

//Uses Swift1.2 syntax with the new if-let // so it won''t compile on a lower version. extension UIAlertController { func show() { present(animated: true, completion: nil) } func present(#animated: Bool, completion: (() -> Void)?) { if let rootVC = UIApplication.sharedApplication().keyWindow?.rootViewController { presentFromController(rootVC, animated: animated, completion: completion) } } private func presentFromController(controller: UIViewController, animated: Bool, completion: (() -> Void)?) { if let navVC = controller as? UINavigationController, let visibleVC = navVC.visibleViewController { presentFromController(visibleVC, animated: animated, completion: completion) } else { if let tabVC = controller as? UITabBarController, let selectedVC = tabVC.selectedViewController { presentFromController(selectedVC, animated: animated, completion: completion) } else { controller.presentViewController(self, animated: animated, completion: completion) } } } }


En Swift 3

let alertLogin = UIAlertController.init(title: "Your Title", message:"Your message", preferredStyle: .alert) alertLogin.addAction(UIAlertAction(title: "Done", style:.default, handler: { (AlertAction) in })) self.window?.rootViewController?.present(alertLogin, animated: true, completion: nil)


Rápido

let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert) //... var rootViewController = UIApplication.shared.keyWindow?.rootViewController if let navigationController = rootViewController as? UINavigationController { rootViewController = navigationController.viewControllers.first } if let tabBarController = rootViewController as? UITabBarController { rootViewController = tabBarController.selectedViewController } //... rootViewController?.present(alertController, animated: true, completion: nil)

C objetivo

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Title" message:@"message" preferredStyle:UIAlertControllerStyleAlert]; //... 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:alertController animated:YES completion:nil];


Swift 4+

Solución que uso durante años sin ningún problema. En primer lugar, extiendo UIWindow para encontrar que es visibleViewController. NOTA : si usa clases de colección * personalizadas (como el menú lateral), debe agregar un controlador para este caso en la siguiente extensión. Después de obtener el mejor controlador de vista, es fácil presentar UIAlertController al igual que UIAlertView .

extension UIAlertController { func show(animated: Bool = true, completion: (() -> Void)? = nil) { if let visibleViewController = UIApplication.shared.keyWindow?.visibleViewController { visibleViewController.present(self, animated: animated, completion: completion) } } } extension UIWindow { var visibleViewController: UIViewController? { guard let rootViewController = rootViewController else { return nil } return visibleViewController(for: rootViewController) } private func visibleViewController(for controller: UIViewController) -> UIViewController { var nextOnStackViewController: UIViewController? = nil if let presented = controller.presentedViewController { nextOnStackViewController = presented } else if let navigationController = controller as? UINavigationController, let visible = navigationController.visibleViewController { nextOnStackViewController = visible } else if let tabBarController = controller as? UITabBarController, let visible = (tabBarController.selectedViewController ?? tabBarController.presentedViewController) { nextOnStackViewController = visible } if let nextOnStackViewController = nextOnStackViewController { return visibleViewController(for: nextOnStackViewController) } else { return controller } } }


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

Con esto, puede presentar fácilmente su alerta así

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

Una cosa a tener en cuenta es que si hay un UIAlertController que se muestra actualmente, UIApplication.topMostViewController devolverá a UIAlertController . Presentar encima de un UIAlertController tiene un comportamiento extraño y debe evitarse. Como tal, debe verificarlo manualmente !(UIApplication.topMostViewController is UIAlertController) antes de presentarlo o agregar un else if caso para devolver cero si 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 } } }