ios - actionsheet - uialertcontroller
Descartar UIAlertViews al ingresar el estado de fondo (12)
¿eh? No he intentado esto todavía, pero me pregunto si tendría sentido crear una subclase de UIAlertView que escuche esta Notificación y se cierre solo si es así ...
Eso tendría el "automáticamente" sin retener / mantenerlo alrededor de la característica que OP está solicitando. Asegúrese de anular el registro de la notificación al cerrar (¡de lo contrario, boom!)
Apple recomienda descartar cualquier UIAlertViews/UIActionSheets
al ingresar el estado de fondo en iOS 4. Esto es para evitar cualquier confusión por parte del usuario cuando relanza la aplicación más tarde. Me pregunto cómo podría descartar elegantemente todos los UIAlertViews a la vez, sin retener una referencia cada vez que configuré uno ...
Alguna idea ?
Como alguien mencionó en un comentario: ¡la respuesta aceptada no es la mejor / más limpia desde iOS 4.0 cuando tenemos bloques! Así es como lo hago:
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Alert!" message:@"This alert will dismiss when application resigns active!" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification){
[alert dismissWithClickedButtonIndex:0 animated:NO];
}];
Crear categoría en la vista de UIAlert
Utilice el método http://nshipster.com/method-swizzling/ Swizzle "show"
Mantenga un registro de la vista de alerta que se muestra al mantener las referencias de semana en el conjunto.
- Cuando desee eliminar todos los datos, llame a Descartar en las vistas de alerta guardadas y vacíe una matriz.
Estaba intrigado por la respuesta de papá (nombre de usuario divertido :), y curioso por qué fue votado negativamente.
Así que lo intenté.
Aquí está la parte .m de una subclase de UIAlertView.
Editar: (Cédric) He agregado una forma de atrapar llamadas para delegar métodos y eliminar al observador para evitar registros múltiples en el centro de notificaciones.
Todo incluido en una clase en este repositorio github: https://github.com/sdarlington/WSLViewAutoDismiss
#import "UIAlertViewAutoDismiss.h"
#import <objc/runtime.h>
@interface UIAlertViewAutoDismiss () <UIAlertViewDelegate> {
id<UIAlertViewDelegate> __unsafe_unretained privateDelegate;
}
@end
@implementation UIAlertViewAutoDismiss
- (id)initWithTitle:(NSString *)title
message:(NSString *)message
delegate:(id)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...
{
self = [super initWithTitle:title
message:message
delegate:self
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:nil, nil];
if (self) {
va_list args;
va_start(args, otherButtonTitles);
for (NSString *anOtherButtonTitle = otherButtonTitles; anOtherButtonTitle != nil; anOtherButtonTitle = va_arg(args, NSString *)) {
[self addButtonWithTitle:anOtherButtonTitle];
}
privateDelegate = delegate;
}
return self;
}
- (void)dealloc
{
privateDelegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
[super dealloc];
}
- (void)setDelegate:(id)delegate
{
privateDelegate = delegate;
}
- (id)delegate
{
return privateDelegate;
}
- (void)show
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[super show];
}
- (void)applicationDidEnterBackground:(NSNotification *)notification
{
[super dismissWithClickedButtonIndex:[self cancelButtonIndex] animated:NO];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}
#pragma mark - UIAlertViewDelegate
// The code below avoids to re-implement all protocol methods to forward to the real delegate.
- (id)forwardingTargetForSelector:(SEL)aSelector
{
struct objc_method_description hasMethod = protocol_getMethodDescription(@protocol(UIAlertViewDelegate), aSelector, NO, YES);
if (hasMethod.name != NULL) {
// The method is that of the UIAlertViewDelegate.
if (aSelector == @selector(alertView:didDismissWithButtonIndex:) ||
aSelector == @selector(alertView:clickedButtonAtIndex:))
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
return privateDelegate;
}
else {
return [super forwardingTargetForSelector:aSelector];
}
}
@end
Funciona bien Es genial, porque puedes comenzar a usarlo de la misma manera que solías usar UIAlertView.
No he tenido tiempo de probarlo a fondo, pero no noté ningún efecto secundario.
He resuelto esto con el siguiente código:
/* taken from the post above (Cédric)*/
- (void)checkViews:(NSArray *)subviews {
Class AVClass = [UIAlertView class];
Class ASClass = [UIActionSheet class];
for (UIView * subview in subviews){
NSLog(@"Class %@", [subview class]);
if ([subview isKindOfClass:AVClass]){
[(UIAlertView *)subview dismissWithClickedButtonIndex:[(UIAlertView *)subview cancelButtonIndex] animated:NO];
} else if ([subview isKindOfClass:ASClass]){
[(UIActionSheet *)subview dismissWithClickedButtonIndex:[(UIActionSheet *)subview cancelButtonIndex] animated:NO];
} else {
[self checkViews:subview.subviews];
}
}
}
/*go to background delegate*/
- (void)applicationDidEnterBackground:(UIApplication *)application
{
for (UIWindow* window in [UIApplication sharedApplication].windows) {
NSArray* subviews = window.subviews;
[self checkViews:subviews];
}
}
La manera directa es mantener una referencia al UIAlertView para que pueda descartarlo. Por supuesto, como Petert mencionó, puedes hacerlo con una Notificación o usar el método delegado en UIApplication
applicationWillResignActive:
no siempre significa que vas a ir al fondo. Por ejemplo, también recibirás esa llamada y notificación de delegado (obtienes ambas) cuando el usuario recibe una llamada telefónica o recibe un SMS. Por lo tanto, debe decidir qué sucede si el usuario recibe un mensaje de texto y presiona cancelar para quedarse en su aplicación. Tal vez quieras asegurarte de que tu UIAlertView aún esté allí.
Así que descartaría el UIAlertView y guardaría el estado en la llamada de delegado cuando realmente va en segundo plano:
applicationDidEnterBackground:
Eche un vistazo a la Sesión 105: Adoptar la multitarea en iOS4 de WWDC10 disponible de forma gratuita en developer.apple.com. Se pone interesante a las 16:00 min
Echa un vistazo a este graphic para comprender los diferentes estados de una aplicación
Mi llamada sería agregar una categoría a UIAlertview agregando la siguiente función:
- (void) hide {
[self dismissWithClickedButtonIndex:0 animated:YES];
}
Y suscribirse a UIApplicationWillResignActiveNotification
:
[[NSNotificationCenter defaultCenter] addObserver:alertView selector:@selector(hide) name:@"UIApplicationWillResignActiveNotification" object:nil];
Tengo esto en mi lista de cosas por hacer, pero mi primer instinto sería escuchar la UIApplicationWillResignActiveNotification
(ver UIApplication) en las vistas donde tienes cosas como UIAlertView - aquí puedes eliminar programáticamente la vista de alerta con:
(void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated
¡La discusión de este método incluso sugiere lo que es para iOS4!
En iPhone OS 4.0, es posible que desee llamar a este método siempre que su aplicación se mueva a un segundo plano. Una vista de alerta no se descarta automáticamente cuando una aplicación se mueve al fondo. Este comportamiento difiere de las versiones anteriores del sistema operativo, donde se cancelaron automáticamente cuando finalizó la aplicación. Descartar la vista de alerta le da a su aplicación la posibilidad de guardar cambios o cancelar la operación y realizar cualquier limpieza necesaria en caso de que su aplicación finalice más tarde.
UIAlertView quedó en desuso en iOS 8 a favor del UIAlertController. Desafortunadamente, esto resulta ser un problema complicado porque la solución aceptada no funcionará, ya que Apple explícitamente no admite la subclasificación de UIAlertController:
La clase UIAlertController está diseñada para ser usada tal como está y no es compatible con la creación de subclases. La jerarquía de vista para esta clase es privada y no debe modificarse.
Mi solución es simplemente atravesar el árbol del controlador de vista y descartar todos los controladores UIAlertControllers que encuentre. Puede habilitar esto globalmente al crear una extensión de UIApplication y luego llamarlo en el método AppDelegate applicationDidEnterBackground
.
Pruebe esto (en Swift):
extension UIApplication
{
class func dismissOpenAlerts(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController)
{
//If it''s an alert, dismiss it
if let alertController = base as? UIAlertController
{
alertController.dismissViewControllerAnimated(false, completion: nil)
}
//Check all children
if base != nil
{
for controller in base!.childViewControllers
{
if let alertController = controller as? UIAlertController
{
alertController.dismissViewControllerAnimated(false, completion: nil)
}
}
}
//Traverse the view controller tree
if let nav = base as? UINavigationController
{
dismissOpenAlerts(nav.visibleViewController)
}
else if let tab = base as? UITabBarController, let selected = tab.selectedViewController
{
dismissOpenAlerts(selected)
}
else if let presented = base?.presentedViewController
{
dismissOpenAlerts(presented)
}
}
}
Y luego en su AppDelegate:
func applicationDidEnterBackground(application: UIApplication)
{
UIApplication.dismissOpenAlerts()
}
Un enfoque totalmente diferente es una búsqueda recursiva.
Función recursiva para su delegado de aplicación
- (void)checkViews:(NSArray *)subviews {
Class AVClass = [UIAlertView class];
Class ASClass = [UIActionSheet class];
for (UIView * subview in subviews){
if ([subview isKindOfClass:AVClass]){
[(UIAlertView *)subview dismissWithClickedButtonIndex:[(UIAlertView *)subview cancelButtonIndex] animated:NO];
} else if ([subview isKindOfClass:ASClass]){
[(UIActionSheet *)subview dismissWithClickedButtonIndex:[(UIActionSheet *)subview cancelButtonIndex] animated:NO];
} else {
[self checkViews:subview.subviews];
}
}
}
Llamar desde el procedimiento applicationDidEnterBackground
[self checkViews:application.windows];
Una solución alternativa, basada en la answer plkEL, donde el observador se elimina cuando la aplicación se pone en segundo plano. Si el usuario descarta la alerta al presionar un botón, el observador seguirá activo, pero solo hasta que la aplicación se ponga en segundo plano (donde se ejecuta el bloque, con una "nil alertView" y se quitó el observador).
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:alertDelegate
cancelButtonTitle:cancelButtonText
otherButtonTitles:okButtonText, nil];
[alert show];
__weak UIAlertView *weakAlert = alert;
__block __weak id observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue: [NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification){
[weakAlert dismissWithClickedButtonIndex:[weakAlert cancelButtonIndex] animated:NO];
[[NSNotificationCenter defaultCenter] removeObserver:observer];
observer = nil;
}];
si solo tiene una o dos ventanas de alerta específicas que muestra (al igual que la mayoría de las aplicaciones), entonces puede simplemente crear una assign
ivar a la alerta:
@property (nonatomic, assign) UIAlertView* alertview;
Luego, en el delegado de la aplicación:
[self.viewController.alertview dismissWithClickedButtonIndex:[self.viewController.alertview cancelButtonIndex] animated:NO];
Puede poner esto en applicationDidEnterBackground:
o donde lo considere oportuno. Cierra la alerta mediante programación al salir de la aplicación. He estado haciendo esto y funciona muy bien.