进入后台状态时关闭 UIAlertViews

发布于 2024-09-06 23:11:11 字数 172 浏览 8 评论 0原文

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

有什么想法吗?

Apple recommends dismissing any UIAlertViews/UIActionSheets when entering background state in iOS 4. This is to avoid any confusion on the user's part when he relaunches the application later. I wonder how I could elegantly dismiss all UIAlertViews at once, without retaining a reference to it everytime I set one up...

Any idea ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

浅浅淡淡 2024-09-13 23:11:11

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

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

并订阅 UIApplicationWillResignActiveNotification

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

My call would be to add a category to UIAlertview adding the following function :

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

And to suscribe to UIApplicationWillResignActiveNotification :

[[NSNotificationCenter defaultCenter] addObserver:alertView selector:@selector(hide) name:@"UIApplicationWillResignActiveNotification" object:nil];
情绪少女 2024-09-13 23:11:11

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

所以我尝试了一下。

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

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

I was intrigued by Dad's answer (funny username :), and curious why it was down-voted.

So I tried it.

Here is the .m part of a subclass of UIAlertView.

Edit: (Cédric) I have added a way to catch calls to delegate methods and remove the observer then to avoid multiple registrations to the notification center.

Everything bundled in a class in this github repo: 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

It works nicely.
It's great, because you can just start using it the same way that you used to use UIAlertView.

I haven't had time to test it thoroughly, but I didn't notice any side effect.

怀念你的温柔 2024-09-13 23:11:11

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

应用程序委托的递归函数

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

A totally different approach is a recursive search.

Recursive function for your application delegate

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

Calling it from the applicationDidEnterBackground procedure

[self checkViews:application.windows];
冬天旳寂寞 2024-09-13 23:11:11

呵呵。还没有尝试过这个,但我想知道创建一个 UIAlertView 的子类来侦听此通知并自行关闭(如果是这样)是否有意义...

这将具有“自动”功能,而无需保留/保留它在特征 OP 周围正在请求。确保取消注册关闭通知(否则繁荣!)

huh. Haven't tried this yet, but I wonder if it would make sense to create a subclass of UIAlertView that listens for this Notification and closes itself if so...

That'd have the "automatically" without retaining / keeping it around characteristic OP is requesting. Make sure to unregister for the notification on close (else boom!)

孤云独去闲 2024-09-13 23:11:11

正如有人在评论中提到的:当我们有块时,接受的答案并不是自 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];
    }];

As someone mentioned in a comment: the accepted answer isn't the best/cleanest one since iOS 4.0 when we have blocks! Here's how I do it:

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];
    }];
我恋#小黄人 2024-09-13 23:11:11

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

UIAlertView was deprecated in iOS 8 in favor of the UIAlertController. Unfortunately, this proves to be a tricky problem because the accepted solution won't work, as Apple explicitly doesn't support subclassing UIAlertController:

The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.

My solution is to simply traverse the view controller tree and dismiss all UIAlertControllers that you find. You can enable this globally by creating an extension of UIApplication and then calling it in the AppDelegate applicationDidEnterBackground method.

Try this (in 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)
        }
    }
}

And then in your AppDelegate:

func applicationDidEnterBackground(application: UIApplication)
{
    UIApplication.dismissOpenAlerts()
}
轻拂→两袖风尘 2024-09-13 23:11:11

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

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

I Have had solved this with the following code:

/* 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];
    }
}
北恋 2024-09-13 23:11:11

最简单的方法是保存对 UIAlertView 的引用,以便您可以将其关闭。当然,正如 petert 提到的,您可以使用通知来完成此操作,或者在 UIApplication 上使用委托方法

applicationWillResignActive:

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

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

applicationDidEnterBackground:

请参阅 WWDC10 的第 105 场会议 - 在 iOS4 上采用多任务处理,可在developer.apple.com 上免费获取。 16:00 分钟变得有趣

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

The straightforward way is to hold a reference to the UIAlertView so you can dismiss it. Of course as petert mentioned you can do it with a Notification or use the delegate method on UIApplication

applicationWillResignActive:

does not always mean that you are going to the background. You will for example also receive that delegate call and notification (you get both) when the user gets a phone call or receives and SMS. So you have to decide what should happen if the user gets an SMS and presses cancel to stay in your app. You maybe want to make sure that your UIAlertView is still there.

So I would dismiss the UIAlertView and save the state in the delegate call when you really go into the background:

applicationDidEnterBackground:

Have a look at Session 105 - Adopting Multitasking on iOS4 of WWDC10 available for free at developer.apple.com. It gets interesting at 16:00 min

Check out this graphic to understand the different states of an application

青瓷清茶倾城歌 2024-09-13 23:11:11

我的 TODO 列表中有此内容,但我的第一直觉是在具有 UIAlertView 等内容的视图中监听通知 UIApplicationWillResignActiveNotification (请参阅 UIApplication) - 在这里您可以以编程方式删除警报视图with:

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

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

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

I have this on my TODO list, but my first instinct would be to listen out for the notifcation UIApplicationWillResignActiveNotification (see UIApplication) in the views where you have things like UIAlertView - here you can programmatically remove the alert view with:

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

The discussion for this method even suggests what it's for in iOS4!

In iPhone OS 4.0, you may want to call this method whenever your application moves to the background. An alert view is not dismissed automatically when an application moves to the background. This behavior differs from previous versions of the operating system, where they were canceled automatically when the application was terminated. Dismissing the alert view gives your application a chance to save changes or abort the operation and perform any necessary cleanup in case your application is terminated later.

说谎友 2024-09-13 23:11:11

如果您只显示一两个特定的警报窗口(就像大多数应用程序一样),那么您只需为警报创建一个分配 ivar:

@property (nonatomic, assign) UIAlertView* alertview;

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

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

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

if you only have one or two specific alert windows you show (as do most apps), then you can just create an assign ivar to the alert:

@property (nonatomic, assign) UIAlertView* alertview;

Then, in the app delegate:

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

You can put this in applicationDidEnterBackground: or wherever you see fit. It closes the alert programmatically upon application exit. I've been doing this and it works great.

你是暖光i 2024-09-13 23:11:11

在 UIAlert 视图上创建类别

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

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

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

Create category on UIAlert View

Use http://nshipster.com/method-swizzling/
Swizzle "show" method

Keep track of alert view shown by keeping week references in array.

-
When you want to remove all data call Dismiss on saved alert views and empty an array.

拍不死你 2024-09-13 23:11:11

另一种解决方案是基于 plkEL 的答案,当应用程序置于后台时,观察者将被删除。如果用户通过按下按钮来消除警报,观察者仍将处于活动状态,但前提是应用程序被置于后台(其中运行块 - 使用“nilalertView” - 并且观察者被删除)。

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

An alternative solution, based on plkEL's, answer, where the observer is removed when the app is put in the background. If user dismisses the alert by pressing a button, the observer will still be active, but only until the app is put in the background (where the block is run - with an "nil alertView" - and the observer removed).

    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;
   }];
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文