web-dev-qa-db-ja.com

バックグラウンド状態に入るときにUIAlertViewsを閉じる

AppleはUIAlertViews/UIActionSheets iOS 4でバックグラウンド状態に入るとき。これは、ユーザーが後でアプリケーションを再起動するときにユーザーの混乱を避けるためです。設定するたびに参照を保持せずに、すべてのUIAlertViewを一度にエレガントに閉じることができるのではないかと思います...

何か案が ?

32
François P.

私は お父さんの答え (面白いユーザー名:)に興味をそそられ、なぜ反対票が投じられたのか不思議に思いました。

だから私はそれを試しました。

以下は、UIAlertViewのサブクラスの.m部分です。

編集:(セドリック)デリゲートメソッドへの呼び出しをキャッチし、オブザーバーを削除して、通知センターへの複数の登録を回避する方法を追加しました。

この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を使用するのと同じ方法で使用を開始できるため、すばらしいです。

十分にテストする時間はありませんでしたが、副作用はありませんでした。

24
Guillaume

私の呼び出しは、次の関数を追加してUIAlertviewにカテゴリを追加することです:

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

そしてUIApplicationWillResignActiveNotificationにサブスクライブするには:

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

まったく異なるアプローチは、再帰的な検索です。

アプリケーションデリゲートの再帰関数

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

ええと。まだこれを試していませんが、この通知をリッスンし、それ自体を閉じるUIAlertViewのサブクラスを作成するのは理にかなっているのではないかと思います...

それは、特徴的なOPが要求していることを保持/維持することなく、「自動的に」持つことになります。終了時に通知の登録を必ず解除してください(別のブーム!)

12
Dad

誰かがコメントで述べたように:受け入れられた答えは、ブロックがあるときの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
pIkEL

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

私は次のコードでこれを解決しました:

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

簡単な方法は、UIAlertViewへの参照を保持して、それを破棄できるようにすることです。もちろん、petertが述べたように、通知でそれを行うか、UIApplicationでデリゲートメソッドを使用できます

applicationWillResignActive:

バックグラウンドに行くことを常に意味するわけではありません。たとえば、ユーザーが電話を受けたりSMSを受信したりしたときに、その代理呼び出しと通知も受け取ります(両方が届きます)。したがって、ユーザーがSMSを取得し、[キャンセル]を押してアプリに留まる場合にどうするかを決定する必要があります。UIAlertViewがまだそこにあることを確認したい場合があります。

したがって、UIAlertViewを閉じて、実際にバックグラウンドに入ったときにデリゲート呼び出しに状態を保存します。

applicationDidEnterBackground:

セッション105-WWDC10のiOS4でのマルチタスクの採用をdeveloper.Apple.comから無料で入手できます。 16:00に面白くなる

アプリケーションのさまざまな状態を理解するには、これをチェックしてください グラフィック

3
GorillaPatch

これはTODOリストにありますが、最初の本能は、UIAlertViewなどのビューで通知UIApplicationWillResignActiveNotification(UIApplicationを参照)をリッスンすることです。ここで、プログラムでアラートビューを削除できます。

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

このメソッドの説明では、iOS4の目的も示唆されています。

IPhone OS 4.0では、アプリケーションがバックグラウンドに移行するたびにこのメソッドを呼び出すことができます。アプリケーションがバックグラウンドに移動しても、アラートビューは自動的に閉じられません。この動作は、アプリケーションが終了したときに自動的にキャンセルされた以前のバージョンのオペレーティングシステムとは異なります。アラートビューを閉じると、アプリケーションが変更を保存したり、操作を中止したり、後でアプリケーションが終了した場合に必要なクリーンアップを実行したりできます。

1
petert

UIAlertビューでカテゴリを作成する

http://nshipster.com/method-swizzling/ Swizzle "show" methodを使用します

週の参照を配列で保持することにより、表示されるアラートビューを追跡します。

-すべてのデータを削除する場合は、保存されたアラートビューでDismissを呼び出し、配列を空にします。

0
Rohit Ragmahale

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

表示する特定のアラートウィンドウが1つまたは2つしかない場合(ほとんどのアプリと同様)、アラートにassign ivarを作成できます。

@property (nonatomic, assign) UIAlertView* alertview;

次に、アプリのデリゲートで:

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

これをapplicationDidEnterBackground:またはあなたが合うと思うところならどこでも。アプリケーションの終了時にプログラムでアラートを閉じます。私はこれをやっていて、それは素晴らしい働きをします。

0
johnbakers