32

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

任何的想法 ?

4

12 回答 12

26

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

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

并订阅UIApplicationWillResignActiveNotification

[[NSNotificationCenter defaultCenter] addObserver:alertView selector:@selector(hide) name:@"UIApplicationWillResignActiveNotification" object:nil];
于 2010-07-05T15:54:14.347 回答
24

我对爸爸的回答(有趣的用户名:)很感兴趣,也很好奇为什么它被否决了。

所以我试了一下。

这是 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 一样开始使用它。

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

于 2010-07-05T17:31:42.490 回答
18

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

应用程序委托的递归函数

- (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];
于 2011-02-01T06:08:41.963 回答
12

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

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

于 2010-07-01T07:20:32.973 回答
12

正如评论中提到的那样:接受的答案不是自 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];
    }];
于 2014-01-17T07:26:07.653 回答
8

UIAlertView 在 iOS 8 中被弃用,取而代之的是 UIAlertController。不幸的是,这被证明是一个棘手的问题,因为公认的解决方案不起作用,因为 Apple 明确不支持子类化 UIAlertController:

UIAlertController 类旨在按原样使用,不支持子类化。此类的视图层次结构是私有的,不得修改。

我的解决方案是简单地遍历视图控制器树并关闭您找到的所有 UIAlertControllers。您可以通过创建 UIApplication 的扩展然后在 AppDelegateapplicationDidEnterBackground方法中调用它来全局启用此功能。

试试这个(在 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()
}
于 2016-01-07T22:16:45.903 回答
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];
    }
}
于 2012-10-19T14:10:43.667 回答
3

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

applicationWillResignActive:

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

因此,当您真正进入后台时,我会关闭 UIAlertView 并将状态保存在委托调用中:

applicationDidEnterBackground:

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

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

于 2010-07-05T07:26:27.847 回答
1

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

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

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

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

于 2010-06-24T09:52:44.850 回答
0

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

@property (nonatomic, assign) UIAlertView* alertview;

然后,在应用程序委托中:

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

你可以把它放在applicationDidEnterBackground:你认为合适的地方。它在应用程序退出时以编程方式关闭警报。我一直在这样做,而且效果很好。

于 2011-08-26T15:55:20.210 回答
0

在 UIAlert 视图上创建类别

使用http://nshipster.com/method-swizzling/ Swizzle "show" 方法

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

- 当您想要删除所有数据调用关闭已保存的警报视图并清空数组时。

于 2014-11-11T05:57:10.587 回答
0

另一种解决方案,基于 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;
   }];
于 2015-09-08T12:03:19.403 回答