[ios] 뷰 컨트롤러에 없을 때 UIAlertController를 표시하는 방법은 무엇입니까?

시나리오 : 사용자가 뷰 컨트롤러의 버튼을 탭합니다. 뷰 컨트롤러는 내비게이션 스택에서 최상위입니다 (분명히). 탭은 다른 클래스에서 호출 된 유틸리티 클래스 메소드를 호출합니다. 나쁜 일이 발생하고 컨트롤이 뷰 컨트롤러로 돌아 가기 전에 바로 경고를 표시하고 싶습니다.

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

이것은 가능 UIAlertView했지만 (아마도 적절하지는 않습니다).

이 경우, 당신은 어떻게을 제시 할 UIAlertController바로 거기, myUtilityMethod?



답변

몇 달 전에 비슷한 질문을 게시했으며 마침내 문제를 해결했다고 생각합니다. 코드를 보려면 내 게시물 하단의 링크를 따르십시오.

해결책은 추가 UIWindow를 사용하는 것입니다.

UIAlertController를 표시하려는 경우 :

  1. 창을 키 및 보이는 창으로 설정 ( window.makeKeyAndVisible())
  2. 일반 UIViewController 인스턴스를 새 창의 rootViewController로 사용하십시오. ( window.rootViewController = UIViewController())
  3. 창의 rootViewController에 UIAlertController를 제시하십시오.

몇 가지 참고할 사항 :

  • UIWindow를 강력하게 참조해야합니다. 강력하게 참조되지 않으면 릴리스되지 않으므로 표시되지 않습니다. 속성을 사용하는 것이 좋지만 관련 object 로도 성공했습니다 .
  • 시스템 UIAlertControllers를 포함하여 다른 모든 것 위에 창을 표시하기 위해 windowLevel을 설정했습니다. ( window.windowLevel = UIWindowLevelAlert + 1)

마지막으로, 당신이 그것을보고 싶다면 구현이 완료되었습니다.

https://github.com/dbettermann/DBAlertController


답변

WWDC에서 랩 중 하나에 들러서 Apple 엔지니어에게 다음과 같은 질문을했습니다. ” UIAlertController? 를 표시하는 가장 좋은 방법은 무엇입니까 ?” 그리고 그는 그들이이 질문을 많이 받고 있다고 말했다. 그리고 우리는 그들이 그것에 관한 세션을 가져야한다고 농담했다. 그는 내부적으로 애플이 UIWindow투명성을 가진 제품을 만들고 그것을 UIViewController제시하고 UIAlertController있다고 말했다. 기본적으로 Dylan Betterman의 답변에는 무엇이 있습니까?

그러나 하위 클래스를 사용하고 싶지 않았으므로 UIAlertController앱 전체에서 코드를 변경해야하기 때문입니다. 그래서 관련된 객체의 도움으로 Objective-C UIAlertControllershow메소드 를 제공하는 카테고리를 만들었습니다 .

관련 코드는 다음과 같습니다.

#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이 때 생성되는이 파괴됩니다 UIAlertControllerdealloced있다가 유지되는 유일한 객체이기 때문에 UIWindow. 그러나 UIAlertController액션 블록 중 하나에서 경고에 액세스하여 속성에 속성 을 할당 하거나 유지 횟수를 늘리면 UIWindow화면에 그대로 유지되고 UI가 잠 깁니다. 액세스가 필요한 경우를 피하려면 위의 샘플 사용 코드를 참조하십시오 UITextField.

테스트 프로젝트로 깃 허브 저장소를 만들었습니다 : FFGlobalAlertController


답변

빠른

let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
//...
var rootViewController = UIApplication.shared.keyWindow?.rootViewController
if let navigationController = rootViewController as? UINavigationController {
    rootViewController = navigationController.viewControllers.first
}
if let tabBarController = rootViewController as? UITabBarController {
    rootViewController = tabBarController.selectedViewController
}
//...
rootViewController?.present(alertController, animated: true, completion: nil)

목표 -C

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Title" message:@"message" preferredStyle:UIAlertControllerStyleAlert];
//...
id rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
if([rootViewController isKindOfClass:[UINavigationController class]])
{
    rootViewController = ((UINavigationController *)rootViewController).viewControllers.firstObject;
}
if([rootViewController isKindOfClass:[UITabBarController class]])
{
    rootViewController = ((UITabBarController *)rootViewController).selectedViewController;
}
//...
[rootViewController presentViewController:alertController animated:YES completion:nil];


답변

Swift 2.2로 다음을 수행 할 수 있습니다.

let alertController: UIAlertController = ...
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)

그리고 스위프트 3.0 :

let alertController: UIAlertController = ...
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)


답변

및 / 또는 UIAlertController extension모든 경우에 매우 일반적 입니다 . 현재 화면에 모달 VC가있는 경우에도 작동합니다. UINavigationControllerUITabBarController

용법:

//option 1:
myAlertController.show()
//option 2:
myAlertController.present(animated: true) {
    //completion code...
}

이것은 확장입니다 :

//Uses Swift1.2 syntax with the new if-let
// so it won't compile on a lower version.
extension UIAlertController {

    func show() {
        present(animated: true, completion: nil)
    }

    func present(#animated: Bool, completion: (() -> Void)?) {
        if let rootVC = UIApplication.sharedApplication().keyWindow?.rootViewController {
            presentFromController(rootVC, animated: animated, completion: completion)
        }
    }

    private func presentFromController(controller: UIViewController, animated: Bool, completion: (() -> Void)?) {
        if  let navVC = controller as? UINavigationController,
            let visibleVC = navVC.visibleViewController {
                presentFromController(visibleVC, animated: animated, completion: completion)
        } else {
          if  let tabVC = controller as? UITabBarController,
              let selectedVC = tabVC.selectedViewController {
                presentFromController(selectedVC, animated: animated, completion: completion)
          } else {
              controller.presentViewController(self, animated: animated, completion: completion)
          }
        }
    }
}


답변

agilityvision의 답변을 개선 하려면 투명한 루트보기 컨트롤러가있는 창을 만들고 거기에서 경고보기를 제공해야합니다.

그러나 경보 제어기에 조치 가있는 한 창에 대한 참조를 유지할 필요는 없습니다 . 작업 처리기 블록의 마지막 단계로 정리 작업의 일부로 창을 숨기면됩니다. 핸들러 블록에서 창에 대한 참조를 가짐으로써 경보 제어기가 해제되면 끊어지는 임시 순환 참조를 작성합니다.

UIWindow* window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.rootViewController = [UIViewController new];
window.windowLevel = UIWindowLevelAlert + 1;

UIAlertController* alertCtrl = [UIAlertController alertControllerWithTitle:... message:... preferredStyle:UIAlertControllerStyleAlert];

[alertCtrl addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK",@"Generic confirm") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
    ... // do your stuff

    // very important to hide the window afterwards.
    // this also keeps a reference to the window until the action is invoked.
    window.hidden = YES;
}]];

[window makeKeyAndVisible];
[window.rootViewController presentViewController:alertCtrl animated:YES completion:nil];


답변

다음 솔루션은 모든 버전에서 유망한 것으로 보였지만 작동 하지 않았습니다 . 이 솔루션은 WARNING을 생성하고 있습니다.

경고 : 보기가 창 계층 구조에없는 경우 표시하십시오!

https://stackoverflow.com/a/34487871/2369867 => 이것은 유망한 것으로 보입니다. 그러나에 없었 습니다 Swift 3. 그래서 나는 스위프트 3에서 이것을 대답하고 있으며 이것은 템플릿 예제 가 아닙니다 .

함수 안에 붙여 넣은 후에는 완전히 기능적인 코드입니다.

빠른 Swift 3 자체 포함 코드

let alertController = UIAlertController(title: "<your title>", message: "<your message>", preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.cancel, handler: nil))

let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alertController, animated: true, completion: nil)

이것은 Swift 3에서 테스트되고 작동하는 코드입니다.