【问题标题】:Dismissing UIAlertViews when entering background state进入后台状态时关闭 UIAlertViews
【发布时间】:2011-03-07 13:34:35
【问题描述】:

Apple 建议在 iOS 4 中进入后台状态时关闭任何 UIAlertViews/UIActionSheets。这是为了避免用户在稍后重新启动应用程序时产生任何混淆。我想知道如何优雅地一次关闭所有 UIAlertViews,而无需在每次设置时都保留对它的引用...

有什么想法吗?

【问题讨论】:

    标签: ios ios4 background uialertview uiactionsheet


    【解决方案1】:

    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'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()
    }
    

    【讨论】:

    • 乍一看我可能遗漏了一些东西,但是有什么理由为什么这个递归函数应该返回 UIViewController?
    • @AndriyGordiychuk 好电话,我通过移植另一个我写的方法来获得最顶层的视图控制器,但忘记删除返回。已更新,谢谢。
    【解决方案2】:

    基于 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;
       }];
    

    【讨论】:

      【解决方案3】:

      在 UIAlert 视图上创建类别

      使用http://nshipster.com/method-swizzling/ Swizzle“显示”方法

      通过将周引用保存在数组中来跟踪显示的警报视图。

      - 当您要删除所有数据时,在保存的警报视图上调用 Dismiss 并清空数组。

      【讨论】:

        【解决方案4】:

        我对 Dad's answer(有趣的用户名 :) 很感兴趣,很好奇为什么它被否决了。

        所以我试了一下。

        这是 UIAlertView 子类的 .m 部分。

        编辑: (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 一样开始使用它。

        我没有时间彻底测试它,但我没有注意到任何副作用。

        【讨论】:

        • 我要做的唯一改变是在 show 方法而不是 init 中注册观察者。
        • @Gotosleep 好点,它会避免调用 -dismissWithClickedButtonIndex: animated: on an not visible alert。暂时不要觉得有问题,但是为了以后的兼容性会更好。在这种情况下,请确保删除未注册的观察者是可以的,或者更好地调整代码以避免它。
        • 顺便说一句,这是一个很好的例子,它使用新的块语法代替了命名错误的 applicationWillResignActive 方法。为什么你用发件人定义if而不使用它?我认为这段代码设计得不好,无论是在实现方式上还是在概念上。
        • 如果有人想使用这样的代码,我在 github 上创建了一个实现它的存储库以及 UIActionSheet 的等效代码:github.com/sdarlington/UIViewAutoDismiss
        • 这对我来说效果很好,但是我能够简化它。删除除 deallocshowapplicationDidEnterBackground: 方法之外的所有内容。删除 privateDelegate 并让调用者定义委托。 UIAlertView 对象在show 中注册通知,并在dealloc 中注销。一切对我来说都很完美。我能看到的唯一问题是,如果你持有 UIAlertView 引用的时间比它可见的时间长得多。
        【解决方案5】:

        一种完全不同的方法是递归搜索。

        应用程序委托的递归函数

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

        【讨论】:

        • 对我来说效果很好。不过,我确实将它重构为两个 UIView 类别方法。 - (void)dismissActionSheetsSubviews:(BOOL)animated- (void)dismissAlertViewSubviews:(BOOL)animated {.
        • 不幸的是,这在 iOS7 中不再有效,因为 UIalertviews 不再是应用程序窗口之一的子视图
        • @fibnochi:请在您的编辑中做出新的回答或发表评论。以如此激烈的方式更改原始帖子确实没有任何意义。谢谢!
        【解决方案6】:

        正如评论中提到的那样:接受的答案不是自 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];
            }];
        

        【讨论】:

        • 我想知道为什么这个答案没有得到更多的支持。它工作得很好而且非常简单。这是一个糟糕的解决方案,我不明白为什么?
        • 我不确定,但是当警报被解除时,观察者是否应该被移除?
        • 这个没有被更多人赞成的原因是,你必须对你在应用程序中显示的每个警报视图执行此操作。一些应用程序提供了许多 UIAlertViews,到处都这样做会很乏味。
        • @wsidell 这就是您利用 CLANG 并创建类似[alert showWithAutoDismiss]; 的快捷方式的时候,其中showWithAutoDismiss 全部使用上述逻辑在一个 CLANG 声明中定义。 :D
        【解决方案7】:

        我已经用以下代码解决了这个问题:

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

        【讨论】:

        • 不幸的是,这在 iOS7 中不再有效,因为 UIAlertViews 无法作为应用程序窗口的子视图找到
        • 有人为 iOS7 提供了一个不错的解决方案吗?
        【解决方案8】:

        如果您只显示一个或两个特定的警报窗口(大多数应用程序也是如此),那么您只需为警报创建一个assign ivar:

        @property (nonatomic, assign) UIAlertView* alertview;
        

        然后,在应用委托中:

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

        您可以将其放入applicationDidEnterBackground: 或您认为合适的任何位置。它在应用程序退出时以编程方式关闭警报。我一直在这样做,效果很好。

        【讨论】:

          【解决方案9】:

          我的电话是向 UIAlertview 添加一个类别,添加以下功能:

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

          订阅UIApplicationWillResignActiveNotification

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

          【讨论】:

          • 谢谢,这让我对如何实现我想要的有了一个好主意。我可能会在一个类别中添加两个方法 - (void) showAndHidAutomatically,它将注册自身并调用 show,在收到通知时将调用另一个名为 hideAndUnregister 的方法,它会执行此操作。享受您的赏金奖励:-)
          • 我做了一些修改:[[NSNotificationCenter defaultCenter] addObserver:alert selector:@selector(hide) name:UIApplicationDidEnterBackgroundNotification object:nil];
          • 我有一个问题。我在 hide-method 中删除了一个观察者。没关系。但是如果 AlertView 被用户关闭(方法“隐藏”没有调用),如何删除观察者?在 NoificationCenter 中存储了已关闭的 AlertView 的无效引用?
          【解决方案10】:

          直接的方法是保存对 UIAlertView 的引用,以便您可以将其关闭。当然,正如彼得提到的,您可以使用 Notification 或使用 UIApplication 上的委托方法来完成

          applicationWillResignActive:
          

          并不总是意味着您要进入后台。例如,当用户接到电话或收到短信时,您还将收到该委托呼叫和通知(您会收到两者)。因此,您必须决定如果用户收到 SMS 并按下取消以留在您的应用程序中会发生什么。您可能想确保您的 UIAlertView 仍然存在。

          所以当你真正进入后台时,我会关闭 UIAlertView 并在委托调用中保存状态:

          applicationDidEnterBackground:
          

          查看 Session 105 - Adopting Multitasking on iOS4 of WWDC10,可在 developer.apple.com 免费获取。 16:00 开始变得有趣

          查看此graphic 以了解应用程序的不同状态

          【讨论】:

            【解决方案11】:

            嗯。还没有尝试过,但我想知道创建一个 UIAlertView 的子类是否有意义,它会监听此通知并自行关闭...

            这将具有“自动”,而无需保留/保持在 OP 要求的特征附近。确保在关闭时取消注册通知(否则繁荣!)

            【讨论】:

            • “太棒了!”感谢 Guillaume 花时间编写我没有时间编写的测试代码。 stackoverflow.com/questions/3105974/… 很高兴它像我想象的那样工作。对我来说,这似乎是一个非常干净和完整的答案。优雅甚至(?),如果我自己这么说的话:-?
            • 这是一个优雅的解决方案。它显示了对 Objective-C 优势的欣赏。
            【解决方案12】:

            我的 TODO 列表上有这个,但我的第一反应是在有 UIAlertView 之类的视图中监听通知 UIApplicationWillResignActiveNotification(请参阅 UIApplication) - 在这里您可以通过以下方式以编程方式删除警报视图:

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

            对这种方法的讨论甚至暗示了它在 iOS4 中的用途!

            在 iPhone OS 4.0 中,您可能希望在应用程序移至后台时调用此方法。当应用程序移到后台时,警报视图不会自动关闭。此行为与以前版本的操作系统不同,后者在应用程序终止时会自动取消。关闭警报视图使您的应用程序有机会保存更改或中止操作并执行任何必要的清理,以防您的应用程序稍后终止。

            【讨论】:

            • 但是弗朗索瓦要求解决方案而不保留 UIAlertView,如果你不保留(即保留计数 = 0),你永远不能调用解雇。我没有尝试但是当你不叫解雇时的行为是什么?当您将应用移回前台时,它会再次出现吗?
            • 好点,我也通过[alertView release]关注[alertView show]UIAlertView 的文档说“现在,由您决定是关闭警报视图(并执行其取消处理程序)还是让它在您的应用程序移回前台时可见。请记住,您的应用程序仍然可以在后台终止,因此在任何一种情况下都可能需要执行某种类型的操作。”。所以我想你需要决定是否保留参考。
            猜你喜欢
            • 2022-09-28
            • 1970-01-01
            • 1970-01-01
            • 2023-03-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-02-07
            相关资源
            最近更新 更多