ビューコントローラにないときに UIAlertController を表示するにはどうすればよいですか? 質問する

ビューコントローラにないときに UIAlertController を表示するにはどうすればよいですか? 質問する

シナリオ: ユーザーがビュー コントローラーのボタンをタップします。ビュー コントローラーは (当然ですが) ナビゲーション スタックの最上位にあります。タップすると、別のクラスで呼び出されるユーティリティ クラス メソッドが呼び出されます。そこで問題が発生したため、制御がビュー コントローラーに戻る前に、その場でアラートを表示したいと考えています。

+ (void)myUtilityMethod {
    // do stuff
    // something bad happened, display an alert.
}

これは可能でしたUIAlertView(ただし、おそらく完全に適切ではありませんでした)。

この場合、UIAlertControllerを の中にどのように表示すればよいでしょうかmyUtilityMethod?

ベストアンサー1

WWDC で、私はラボの 1 つに立ち寄って、Apple のエンジニアに同じ質問をしました。「 を表示するためのベスト プラクティスは何ですかUIAlertController?」 すると、この質問はよく寄せられるから、この件についてセッションを開くべきだと冗談を言ったそうです。Apple 社内ではUIWindow透明な を作成しUIViewController、その上に を表示していると彼は言いましたUIAlertController。基本的には、Dylan Betterman の回答と同じです。

しかし、 のサブクラスは使いたくありませんでしたUIAlertController。そうすると、アプリ全体のコードを変更する必要が出てくるからです。そこで、関連オブジェクトの助けを借りて、 Objective-C でメソッドUIAlertControllerを提供するのカテゴリを作成しましたshow

関連するコードは次のとおりです。

#import "UIAlertController+Window.h"
#import <objc/runtime.h>

@interface UIAlertController (Window)

- (void)show;
- (void)show:(BOOL)animated;

@end

@interface UIAlertController (Private)

@property (nonatomic, strong) UIWindow *alertWindow;

@end

@implementation UIAlertController (Private)

@dynamic alertWindow;

- (void)setAlertWindow:(UIWindow *)alertWindow {
    objc_setAssociatedObject(self, @selector(alertWindow), alertWindow, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIWindow *)alertWindow {
    return objc_getAssociatedObject(self, @selector(alertWindow));
}

@end

@implementation UIAlertController (Window)

- (void)show {
    [self show:YES];
}

- (void)show:(BOOL)animated {
    self.alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.alertWindow.rootViewController = [[UIViewController alloc] init];

    id<UIApplicationDelegate> delegate = [UIApplication sharedApplication].delegate;
    // Applications that does not load with UIMainStoryboardFile might not have a window property:
    if ([delegate respondsToSelector:@selector(window)]) {
        // we inherit the main window's tintColor
        self.alertWindow.tintColor = delegate.window.tintColor;
    }

    // window level is above the top window (this makes the alert, if it's a sheet, show over the keyboard)
    UIWindow *topWindow = [UIApplication sharedApplication].windows.lastObject;
    self.alertWindow.windowLevel = topWindow.windowLevel + 1;

    [self.alertWindow makeKeyAndVisible];
    [self.alertWindow.rootViewController presentViewController:self animated:animated completion:nil];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    
    // precaution to ensure window gets destroyed
    self.alertWindow.hidden = YES;
    self.alertWindow = nil;
}

@end

使用例は次のとおりです。

// need local variable for TextField to prevent retain cycle of Alert otherwise UIWindow
// would not disappear after the Alert was dismissed
__block UITextField *localTextField;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Global Alert" message:@"Enter some text" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
    NSLog(@"do something with text:%@", localTextField.text);
// do NOT use alert.textfields or otherwise reference the alert in the block. Will cause retain cycle
}]];
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
    localTextField = textField;
}];
[alert show];

作成されたはが解放UIWindowされると破棄されますUIAlertController。これは が を保持している唯一のオブジェクトだからですUIWindow。ただし、 をUIAlertControllerプロパティに割り当てたり、アクション ブロックの 1 つでアラートにアクセスして の保持カウントを増やしたりすると、 はUIWindow画面に表示されたままになり、UI がロックされます。 にアクセスする必要がある場合は、上記の使用例コードを参照して回避してくださいUITextField

テスト プロジェクトを含む GitHub リポジトリを作成しました。FFグローバルアラートコントローラ

おすすめ記事