web-dev-qa-db-fra.com

Suppression de UIAlertView lors de la saisie en arrière-plan

Apple recommande de ne pas utiliser UIAlertViews/UIActionSheets lors de la saisie de l'état d'arrière-plan dans iOS 4. Ceci permet d'éviter toute confusion de la part de l'utilisateur qui relancera l'application ultérieurement. Je me demande comment je pourrais élégamment rejeter tous les UIAlertViews à la fois, sans garder de référence à chaque fois que j'en ai créé un ...

Une idée ?

31
François P.

Je suis intriguée par réponse de papa (nom d'utilisateur amusant :) et curieuse de savoir pourquoi il a été voté à la baisse.

Alors j'ai essayé. 

Voici la partie .m d’une sous-classe de UIAlertView.

Éditer: (Cédric) J'ai ajouté un moyen d'attraper les appels à déléguer des méthodes et de supprimer l'observateur, puis d'éviter plusieurs inscriptions au centre de notification. 

Tout ce qui est regroupé dans une classe de ce dépôt 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

Cela fonctionne bien. C’est génial, car vous pouvez simplement commencer à l’utiliser de la même manière que vous utilisiez UIAlertView.

Je n'ai pas eu le temps de le tester à fond, mais je n'ai remarqué aucun effet secondaire.

24
Guillaume

Mon appel serait d'ajouter une catégorie à UIAlertview en ajoutant la fonction suivante:

- (void) hide {
  [self dismissWithClickedButtonIndex:0 animated:YES];
}

Et pour vous abonner à UIApplicationWillResignActiveNotification:

[[NSNotificationCenter defaultCenter] addObserver:alertView selector:@selector(hide) name:@"UIApplicationWillResignActiveNotification" object:nil];
26
Charter

Une approche totalement différente est une recherche récursive.

Fonction récursive pour votre délégué d'application

- (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];
        }
    }
}

L'appeler depuis la procédure applicationDidEnterBackground

[self checkViews:application.windows];
19
Wilbert

hein Je n'ai pas encore essayé, mais je me demande s'il serait logique de créer une sous-classe de UIAlertView qui écoute cette notification et se ferme si oui 

Cela aurait le "automatiquement" sans conserver/garder autour de caractéristique que demande OP. Assurez-vous de vous désinscrire pour la notification à la fermeture (sinon boum!)

12
Dad

Comme quelqu'un l'a mentionné dans un commentaire: la réponse acceptée n'est pas la meilleure/la plus propre depuis iOS 4.0 quand nous avons des blocs! Voici comment je le fais:

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];
    }];
12
pIkEL

UIAlertView était obsolète dans iOS 8 au profit de UIAlertController. Malheureusement, cela s'avère être un problème délicat car la solution acceptée ne fonctionnera pas, car Apple ne prend explicitement pas en charge le sous-classement de UIAlertController:

La classe UIAlertController est destinée à être utilisée telle quelle et ne prend pas en charge les sous-classes. La hiérarchie des vues pour cette classe est privée et ne doit pas être modifiée.

Ma solution consiste simplement à parcourir l’arborescence du contrôleur de vue et à supprimer tous les UIAlertControllers que vous avez trouvés. Vous pouvez l'activer globalement en créant une extension de UIApplication, puis en l'appelant dans la méthode AppDelegate applicationDidEnterBackground.

Essayez ceci (dans 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)
        }
    }
}

Et ensuite dans votre AppDelegate:

func applicationDidEnterBackground(application: UIApplication)
{
    UIApplication.dismissOpenAlerts()
}
7
jcady

J'ai résolu ce problème avec le code suivant:

/* 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];
    }
}
7
Ponja

La méthode la plus simple consiste à conserver une référence à UIAlertView afin de pouvoir la supprimer. Bien sûr, comme Petert l'a mentionné, vous pouvez le faire avec une notification ou utiliser la méthode de délégation sur UIApplication.

applicationWillResignActive:

ne signifie pas toujours que vous allez au fond. Vous recevrez par exemple également cet appel de délégué et cette notification (vous obtenez les deux) lorsque l'utilisateur reçoit un appel téléphonique ou reçoit un SMS. Vous devez donc décider de ce qui doit se passer si l'utilisateur reçoit un SMS et appuie sur Annuler pour rester dans votre application. Vous voulez peut-être vous assurer que votre UIAlertView est toujours là.

Je rejetterais donc UIAlertView et enregistrerais l'état dans l'appel délégué lorsque vous passez vraiment à l'arrière-plan:

applicationDidEnterBackground:

Consultez la session 105 - Adopter le multitâche sur iOS4 de WWDC10 disponible gratuitement sur developer.Apple.com. Ça devient intéressant à 16:00 min

Découvrez ce graphique pour comprendre les différents états d'une application

3
GorillaPatch

J'ai ceci sur ma liste TODO, mais mon premier réflexe serait d'écouter la notification UIApplicationWillResignActiveNotification (voir UIApplication) dans les vues où vous avez des éléments comme UIAlertView. Vous pouvez ici supprimer par programmation la vue d'alerte avec:

(void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated

La discussion sur cette méthode suggère même à quoi ça sert dans iOS4!

Dans iPhone OS 4.0, vous pouvez appeler cette méthode chaque fois que votre application passe en arrière-plan. Une vue d'alerte n'est pas automatiquement supprimée lorsqu'une application passe en arrière-plan. Ce comportement diffère des versions précédentes du système d'exploitation, où elles étaient automatiquement annulées lors de la fermeture de l'application. Si vous désactivez l'affichage des alertes, votre application aura la possibilité d'enregistrer les modifications ou d'abandonner l'opération et d'effectuer tout nettoyage nécessaire au cas où votre application serait fermée ultérieurement.

1
petert

Créer une catégorie sur UIAlert View 

Utilisez http://nshipster.com/method-swizzling/ Swizzle, méthode "spectacle" 

Surveillez l'affichage des alertes en conservant les références de semaine dans un tableau.

- Lorsque vous souhaitez supprimer toutes les données, appelez Rejeter dans les vues d'alerte enregistrées et vider un tableau.

0
Rohit Ragmahale

Une solution alternative, basée sur plkEL, answer , dans laquelle l'observateur est supprimé lorsque l'application est mise en arrière-plan. Si l'utilisateur supprime l'alerte en appuyant sur un bouton, l'observateur reste actif, mais uniquement jusqu'à ce que l'application soit mise en arrière-plan (à l'endroit où le bloc est exécuté - avec un "nil alertView" - et que l'observateur soit supprimé).

    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;
   }];
0
ckibsen

si vous n’affichez qu’une ou deux fenêtres d’alerte spécifiques (comme le font la plupart des applications), vous pouvez simplement créer un ivar assign à l’alerte:

@property (nonatomic, assign) UIAlertView* alertview;

Ensuite, dans le délégué de l'application:

[self.viewController.alertview dismissWithClickedButtonIndex:[self.viewController.alertview cancelButtonIndex] animated:NO];

Vous pouvez l'inscrire dans applicationDidEnterBackground: ou à votre guise. Il ferme l'alerte par programmation à la fermeture de l'application. Je fais ça et ça marche très bien.

0
johnbakers