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 vuelva 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 apropiado).
En este caso, ¿cómo presentas un UIAlertController
, allí mismo myUtilityMethod
?
fuente
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 unUIWindow
con un transparenteUIViewController
y luego presentando elUIAlertController
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íaUIAlertController
que proporciona unshow
método en Objective-C.Aquí está el código relevante:
Aquí hay un ejemplo de uso:
El
UIWindow
que se crea se destruirá cuandoUIAlertController
se desasigne, ya que es el único objeto que retiene elUIWindow
. Pero si asigna laUIAlertController
propiedad 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,UIWindow
permanecerá en la pantalla, bloqueando su IU. Consulte el código de uso de muestra anterior para evitar en caso de que necesite accederUITextField
.Hice un repositorio de GitHub con un proyecto de prueba: FFGlobalAlertController
fuente
viewDidDisappear:
en una categoría parece una mala idea. En esencia, estás compitiendo con la implementación del marco de trabajoviewDidDisappear:
. Por ahora puede estar bien, pero si Apple decide implementar ese método en el futuro, no hay forma de que lo llame (es decir, no hay ningún análogo desuper
eso apunta a la implementación primaria de un método desde una implementación de categoría) .prefersStatusBarHidden
ypreferredStatusBarStyle
sin una subclase adicional?Rápido
C objetivo
fuente
Puede hacer lo siguiente con Swift 2.2:
Y Swift 3.0:
fuente
Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!
.visibleViewController
propiedad en cualquier momento para ver desde qué controlador presentar la alerta. Echa un vistazo a los documentosBastante genérico
UIAlertController
extension
para todos los casos deUINavigationController
y / oUITabBarController
. También funciona si hay un VC modal en pantalla en este momento.Uso:
Esta es la extensión:
fuente
UI
clase que tiene una (débil!)currentVC
De tipoUIViewController
.I tienenBaseViewController
que hereda deUIViewController
y conjuntoUI.currentVC
paraself
elviewDidAppear
entoncesnil
sobreviewWillDisappear
. Todos mis controladores de vista en la aplicación heredanBaseViewController
. De esa manera, si tiene algoUI.currentVC
(no esnil
...), definitivamente no está en el medio de una animación de presentación, y puede pedirle que presente suUIAlertController
.else { if let presentedViewController = controller.presentedViewController { presentedViewController.presentViewController(self, animated: animated, completion: completion) } else { controller.presentViewController(self, animated: animated, completion: completion) } }
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.
fuente
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://stackoverflow.com/a/34487871/2369867 => Esto parece prometedor entonces. Pero era no en
Swift 3
. Así que estoy respondiendo esto en Swift 3 y este no es un ejemplo de plantilla.Este es un código bastante funcional por sí solo una vez que pegue dentro de cualquier función.
Esto está probado y funciona en Swift 3.
fuente
UIWindow
o la ventana se abrirá y desaparecerá poco después de salir del alcance.Aquí está la respuesta de mythicalcoder como extensión, probada y trabajando en Swift 4:
Ejemplo de uso:
fuente
Esto funciona en Swift para controladores de vista normales e incluso si hay un controlador de navegación en la pantalla:
fuente
UIWindow
no responde. Algo que ver con lowindowLevel
probable. ¿Cómo puedo hacer que responda?alertWindow
paranil
cuando haya terminado con él.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:
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:
No lo he probado, pero esto también puede ser necesario si su VC raíz es un controlador de navegación.
fuente
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentedViewController?.presentViewController(controller, animated: true, completion: nil)
Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<UIAlertController: 0x15cd4afe0>)
rootViewController.presentedViewController
si no es nulo, el uso de otra manerarootViewController
. Para una solución totalmente genérica, puede ser necesario recorrer la cadena depresentedViewController
s para llegar altopmost
VCLa respuesta de @ agilityvision se tradujo a Swift4 / iOS11. No he usado cadenas localizadas, pero puedes cambiar eso fácilmente:
fuente
window.backgroundColor = UIColor.clear
arreglado eso.viewController.view.backgroundColor = UIColor.clear
no parece ser necesarioUIAlertController
subclases:The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
developer.apple.com/documentation/uikit/uialertcontrollerCrear extensión como en la respuesta de Aviel Gross. Aquí tienes la extensión Objective-C.
Aquí tienes el archivo de encabezado * .h
E implementación: * .m
Está utilizando esta extensión en su archivo de implementación de esta manera:
fuente
Cross post mi respuesta 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:fuente
La respuesta de Zev Eisenberg es simple y directa, pero no siempre funciona, y puede fallar con este mensaje de advertencia:
Esto se debe a que Windows rootViewController 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:
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 nueva acuñada
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.Sin embargo, un artefacto de presentación
keyWindow
es la animación del teclado deslizándose hacia abajo cuando se presenta la alerta, y deslizándose hacia arriba nuevamente cuando se desactiva 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: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.fuente
Swift 4+
Solución que uso durante años sin ningún problema. En primer lugar, me 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 presentarloUIAlertController
comoUIAlertView
.fuente
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 fuerte referencia 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:Puede usar esto como está, o si desea un método conveniente para usted
UIAlertController
, puede incluirlo en una extensión:fuente
dismiss
al WindowAlertPresentationController directamentealert.presentingViewController?.dismiss(animated: true, completion: nil)
Forma abreviada de presentar la alerta en Objective-C:
Donde
alertController
esta tuUIAlertController
objeto?NOTA: También deberá asegurarse de que su clase auxiliar se extienda
UIViewController
fuente
Si alguien está interesado, creé una versión Swift 3 de @agilityvision answer. El código:
fuente
Con esto, puede presentar fácilmente su alerta así
Una cosa a tener en cuenta es que si hay un UIAlertController que se muestra actualmente,
UIApplication.topMostViewController
devolverá aUIAlertController
. Presentar encima de unUIAlertController
tiene un comportamiento extraño y debe evitarse. Como tal, debe verificarlo manualmente!(UIApplication.topMostViewController is UIAlertController)
antes de presentarlo o agregar unelse if
caso para devolver cero siself is UIAlertController
fuente
Puede enviar la vista o controlador actual como parámetro:
fuente
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.
Parece que todo ha funcionado hasta ahora en mis pruebas.
Gracias Kevin!
fuente
Además de las excelentes respuestas dadas ( agilityvision , adib , malhal ). Para alcanzar el comportamiento de colas como en los viejos UIAlertViews (evite la superposición de ventanas de alerta), use este bloque para observar la disponibilidad a nivel de ventana:
Ejemplo completo:
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.
fuente
Intenté todo lo mencionado, pero sin éxito. El método que utilicé para Swift 3.0:
fuente
Algunas de estas respuestas solo funcionaron en parte para mí, combinarlas en el siguiente método de clase en AppDelegate fue la solución para mí. Funciona en iPad, en vistas de UITabBarController, en UINavigationController, y al presentar modales. Probado en iOS 10 y 13.
Uso:
fuente
Soporte de escena iOS13 (cuando se usa UIWindowScene)
fuente
Puede intentar implementar una categoría
UIViewController
con mehtod como- (void)presentErrorMessage;
Y y dentro de ese método implementa UIAlertController y luego presentarloself
. Que en su código de cliente tendrá algo como:[myViewController presentErrorMessage];
De esa forma evitará parámetros innecesarios y advertencias sobre que la vista no está en la jerarquía de ventanas.
fuente
myViewController
en el código donde sucede lo malo. Eso está en un método de utilidad que no sabe nada sobre el controlador de vista que lo llamó.UIAlertView
me llevó a romper esa regla en algunos puntos.Hay 2 enfoques que puede usar:
-Utilizar
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.
Uso:
fuente
Uso este código con algunas pequeñas variaciones personales en mi clase AppDelegate
fuente
Parece funcionar:
fuente
crear clase auxiliar AlertWindow y luego usar como
fuente