Отклонение UIAlertViews при переходе в фоновое состояние - PullRequest
31 голосов
/ 24 июня 2010

Apple рекомендует отклонять любые UIAlertViews/UIActionSheets при переходе в фоновое состояние в iOS 4. Это позволяет избежать путаницы со стороны пользователя, когда он перезапускает приложение позже.Интересно, как я мог бы элегантно закрыть все UIAlertView одновременно, не сохраняя ссылки на него каждый раз, когда я его настраивал ...

Есть идеи?

Ответы [ 12 ]

26 голосов
/ 05 июля 2010

Мой призыв будет добавить категорию в UIAlertview, добавив следующую функцию:

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

И добавить в UIApplicationWillResignActiveNotification:

[[NSNotificationCenter defaultCenter] addObserver:alertView selector:@selector(hide) name:@"UIApplicationWillResignActiveNotification" object:nil];
24 голосов
/ 05 июля 2010

Я был заинтригован Ответом папы (забавное имя пользователя :) и любопытно, почему за него проголосовали.

Так что я попробовал.

Вот часть .m подкласса UIAlertView.

Редактировать: (Седрик) Я добавил способ перехватывать вызовы для делегирования методов и удалять наблюдателя.затем, чтобы избежать нескольких регистраций в центр уведомлений.

Все, что связано в классе в этом репозитории 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

Работает хорошо.Это здорово, потому что вы можете просто начать использовать его так же, как и при использовании UIAlertView.

У меня не было времени тщательно его протестировать, но я не заметил никаких побочных эффектов.

19 голосов
/ 01 февраля 2011

Совершенно другой подход - рекурсивный поиск.

Рекурсивная функция для вашего делегата приложения

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

Вызов его из процедуры приложения DidEnterBackground

[self checkViews:application.windows];
12 голосов
/ 17 января 2014

Как кто-то упомянул в комментарии: принятый ответ не самый лучший / самый чистый со времен iOS 4.0, когда у нас есть блоки!Вот как я это делаю:

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 голосов
/ 01 июля 2010

да.Еще не пробовал, но мне интересно, имеет ли смысл создавать подкласс UIAlertView, который прослушивает это уведомление и закрывает себя, если это так ...

Это будет "автоматически" без сохранения/ сохраняя это вокруг характеристики OP запрашивает.Обязательно отмените регистрацию для уведомления о закрытии (иначе бум!)

8 голосов
/ 08 января 2016

UIAlertView устарел в iOS 8 в пользу UIAlertController. К сожалению, это оказывается сложной задачей, потому что принятое решение не будет работать, так как Apple явно не поддерживает создание подклассов UIAlertController:

Класс UIAlertController предназначен для использования как есть и не поддерживает создание подклассов. Иерархия представления для этого класса является закрытой и не должна быть изменена.

Мое решение состоит в том, чтобы просто пройти по дереву контроллера представления и удалить все найденные UIAlertController. Вы можете включить это глобально, создав расширение UIApplication и затем вызвав его в методе AppDelegate applicationDidEnterBackground.

Попробуйте это (на 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)
        }
    }
}

А затем в вашем AppDelegate:

func applicationDidEnterBackground(application: UIApplication)
{
    UIApplication.dismissOpenAlerts()
}
7 голосов
/ 19 октября 2012

Я решил это с помощью следующего кода:

/* 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];
    }
}
3 голосов
/ 05 июля 2010

Простой способ - сохранить ссылку на UIAlertView, чтобы вы могли отклонить его. Конечно, как упоминал Питер, вы можете сделать это с помощью Уведомления или использовать метод делегата в UIApplication

applicationWillResignActive:

не всегда означает, что вы идете на задний план. Например, вы также получите этот делегатский вызов и уведомление (вы получите оба), когда пользователь получает телефонный звонок или получает и SMS. Поэтому вы должны решить, что должно произойти, если пользователь получит SMS и нажмет «Отмена», чтобы остаться в вашем приложении. Возможно, вы захотите убедиться, что ваш UIAlertView все еще там.

Таким образом, я бы отклонил UIAlertView и сохранил состояние в вызове делегата, когда вы действительно перейдете в фоновый режим:

applicationDidEnterBackground:

Посмотрите на Сессию 105 - Принятие многозадачности на iOS4 WWDC10, доступной бесплатно на developer.apple.com. Становится интересно в 16:00 мин

Проверьте этот рисунок , чтобы понять различные состояния приложения

1 голос
/ 24 июня 2010

У меня есть это в моем списке TODO, но мой первый инстинкт должен был бы выслушать уведомление UIApplicationWillResignActiveNotification (см. UIApplication) в представлениях, где у вас есть такие вещи, как UIAlertView - здесь вы можете программно удалить представление предупреждений с помощью:

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

Обсуждение этого метода даже показывает, для чего он нужен в iOS4!

В iPhone OS 4.0 вы можете вызывать этот метод всякий раз, когда ваше приложение перемещается в фоновый режим,Просмотр предупреждений не отменяется автоматически, когда приложение перемещается в фоновый режим.Это поведение отличается от предыдущих версий операционной системы, где они были отменены автоматически при завершении работы приложения.Отключение представления предупреждений дает вашему приложению возможность сохранить изменения или прервать операцию и выполнить любую необходимую очистку на случай, если ваше приложение будет прекращено позднее.

0 голосов
/ 08 сентября 2015

Альтернативное решение, основанное на plkEL, ответ , где наблюдатель удаляется, когда приложение помещается в фоновом режиме. Если пользователь отклоняет предупреждение нажатием кнопки, наблюдатель будет по-прежнему активен, но только до тех пор, пока приложение не будет помещено в фоновый режим (где выполняется блок - с «nil alertView» - и наблюдатель удален).

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