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:
-
Haga que su ventana sea la clave y la ventana visible (
window.makeKeyAndVisible()
) -
Simplemente use una instancia simple de UIViewController como rootViewController de la nueva ventana.
(
window.rootViewController = UIViewController()
) - 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.
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
}
}
}