Подтвердить что ты не робот

Отключение UIAlertView при входе в фоновое состояние

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

Любая идея?

4b9b3361

Ответ 1

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

Итак, я попробовал.

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

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

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

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

Ответ 2

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

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

И подписать UIApplicationWillResignActiveNotification:

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

Ответ 3

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

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

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

Вызов из процедуры applicationDidEnterBackground

[self checkViews:application.windows];

Ответ 4

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

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

Ответ 5

Как упоминалось в комментарии: принятый ответ не самый лучший/самый чистый с 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];
    }];

Ответ 6

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

/* 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

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

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

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

Попробуйте это (в Swift):

extension UIApplication
{
    class func dismissOpenAlerts(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController)
    {
        //If it 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()
}

Ответ 8

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

applicationWillResignActive:

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

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

applicationDidEnterBackground:

Взгляните на сеанс 105 - Принятие многозадачности на iOS4 из WWDC10 можно бесплатно скачать на developer.apple.com. Он становится интересным в 16:00 мин.

Ознакомьтесь с этим графикой, чтобы понять различные состояния приложения.

Ответ 9

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

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

Обсуждение этого метода даже говорит о том, что он делает в iOS4!

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

Ответ 10

если у вас есть только одно или два определенных окна предупреждений, которые вы показываете (как и большинство приложений), тогда вы можете просто создать assign ivar для предупреждения:

@property (nonatomic, assign) UIAlertView* alertview;

Затем в делете приложения:

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

Вы можете поместить это в applicationDidEnterBackground: или там, где вы считаете нужным. Он программно закрывает оповещение при выходе из приложения. Я делаю это, и он отлично работает.

Ответ 11

Создать категорию в представлении UIAlert

Используйте http://nshipster.com/method-swizzling/ Swizzle "показать" метод

Следите за просмотром предупреждений, показывая, сохраняя ссылки на неделю в массиве.

-  Если вы хотите удалить все вызовы данных Dismiss на сохраненные виды предупреждений и очистить массив.

Ответ 12

Альтернативное решение, основанное на plkEL, answer, где наблюдатель удаляется, когда приложение помещается в фоновом режиме. Если пользователь отклоняет предупреждение, нажимая кнопку, наблюдатель все равно будет активен, но только до тех пор, пока приложение не будет помещено в фоновом режиме (где выполняется блок - с "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;
   }];