Подтвердить что ты не робот

Ios - присутствует UIAlertController поверх всего, независимо от иерархии представлений

Я пытаюсь иметь вспомогательный класс, который представляет UIAlertController. Поскольку это вспомогательный класс, я хочу, чтобы он работал независимо от иерархии представлений и без информации об этом. Я могу показать предупреждение, но когда его увольняют, приложение разбилось на:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Trying to dismiss UIAlertController <UIAlertController: 0x135d70d80>
 with unknown presenter.'

Я создаю всплывающее окно с помощью:

guard let window = UIApplication.shared.keyWindow else { return }
let view = UIView()
view.isUserInteractionEnabled = true
window.insertSubview(view, at: 0)
window.bringSubview(toFront: view)
// add full screen constraints to view ...

let controller = UIAlertController(
  title: "confirm deletion?",
  message: ":)",
  preferredStyle: .alert
)

let deleteAction = UIAlertAction(
  title: "yes",
  style: .destructive,
  handler: { _ in
    DispatchQueue.main.async {
      view.removeFromSuperview()
      completion()
    }
  }
)
controller.addAction(deleteAction)

view.insertSubview(controller.view, at: 0)
view.bringSubview(toFront: controller.view)
// add centering constraints to controller.view ...

Когда я нажимаю yes, приложение выйдет из строя, и обработчик не попадет перед сбоем. Я не могу представить UIAlertController, потому что это будет зависеть от текущей иерархии представлений, в то время как я хочу, чтобы всплывающее окно было независимым

EDIT: быстрое решение Спасибо @Vlad за эту идею. Кажется, что работа в отдельном окне намного проще. Итак, вот работающее решение Swift:

class Popup {
  private var alertWindow: UIWindow
  static var shared = Popup()

  init() {
    alertWindow = UIWindow(frame: UIScreen.main.bounds)
    alertWindow.rootViewController = UIViewController()
    alertWindow.windowLevel = UIWindowLevelAlert + 1
    alertWindow.makeKeyAndVisible()
    alertWindow.isHidden = true
  }

  private func show(completion: @escaping ((Bool) -> Void)) {
    let controller = UIAlertController(
      title: "Want to do it?",
      message: "message",
      preferredStyle: .alert
    )

    let yesAction = UIAlertAction(
      title: "Yes",
      style: .default,
      handler: { _ in
        DispatchQueue.main.async {
          self.alertWindow.isHidden = true
          completion(true)
        }
    })

    let noAction = UIAlertAction(
      title: "Not now",
      style: .destructive,
      handler: { _ in
        DispatchQueue.main.async {
          self.alertWindow.isHidden = true
          completion(false)
        }
    })

    controller.addAction(noAction)
    controller.addAction(yesAction)
    self.alertWindow.isHidden = false
    alertWindow.rootViewController?.present(controller, animated: false)
  }
}
4b9b3361

Ответ 1

Обновление от 23 июля 2019 года:

Видимо, эта техника перестала работать в iOS 13.0. :(

Я обновлю, как только найду время для расследования...

Старая техника:

Вот расширение Swift (5) для него:

public extension UIAlertController {
    func show() {
        let win = UIWindow(frame: UIScreen.main.bounds)
        let vc = UIViewController()
        vc.view.backgroundColor = .clear
        win.rootViewController = vc
        win.windowLevel = UIWindow.Level.alert + 1  // Swift 3-4: UIWindowLevelAlert + 1
        win.makeKeyAndVisible()    
        vc.present(self, animated: true, completion: nil)
    }
}

Просто настройте свой UIAlertController, а затем позвоните:

alert.show()

Больше не ограничено иерархией View Controllers!

Ответ 2

Я скорее представлю его на UIApplication.shared.keyWindow.rootViewController, вместо использования вашей логики. Итак, вы можете сделать следующее:

UIApplication.shared.keyWindow.rootViewController.presentController(yourAlert, animated: true, completion: nil)

Редакция:

У меня есть старая категория ObjC, где я использовал следующий метод show, который я использовал, если никакой контроллер не был предоставлен для представления из:

- (void)show
{
    self.alertWindow = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
    self.alertWindow.rootViewController = [UIViewController new];
    self.alertWindow.windowLevel = UIWindowLevelAlert + 1;
    [self.alertWindow makeKeyAndVisible];
    [self.alertWindow.rootViewController presentViewController: self animated: YES completion: nil];
}

добавлена ​​целая категория, если кому-то это нужно

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

@interface UIAlertController ()
@property (nonatomic, strong) UIWindow* alertWindow;
@end

@implementation UIAlertController (ShortMessage)

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

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

+ (UIAlertController*)showShortMessage: (NSString*)message fromController: (UIViewController*)controller
{
    return [self showAlertWithTitle: nil shortMessage: message fromController: controller];
}

+ (UIAlertController*)showAlertWithTitle: (NSString*)title shortMessage: (NSString*)message fromController: (UIViewController*)controller
{
    return [self showAlertWithTitle: title shortMessage: message actions: @[[UIAlertAction actionWithTitle: @"Ok" style: UIAlertActionStyleDefault handler: nil]] fromController: controller];
}

+ (UIAlertController*)showAlertWithTitle: (NSString*)title shortMessage: (NSString*)message actions: (NSArray<UIAlertAction*>*)actions fromController: (UIViewController*)controller
{
    UIAlertController* alert = [UIAlertController alertControllerWithTitle: title
                                                    message: message
                                             preferredStyle: UIAlertControllerStyleAlert];

    for (UIAlertAction* action in actions)
    {
        [alert addAction: action];
    }

    if (controller)
    {
        [controller presentViewController: alert animated: YES completion: nil];
    }
    else
    {
        [alert show];
    }

    return alert;
}

+ (UIAlertController*)showAlertWithMessage: (NSString*)message actions: (NSArray<UIAlertAction*>*)actions fromController: (UIViewController*)controller
{
    return [self showAlertWithTitle: @"" shortMessage: message actions: actions fromController: controller];
}

- (void)show
{
    self.alertWindow = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
    self.alertWindow.rootViewController = [UIViewController new];
    self.alertWindow.windowLevel = UIWindowLevelAlert + 1;
    [self.alertWindow makeKeyAndVisible];
    [self.alertWindow.rootViewController presentViewController: self animated: YES completion: nil];
}

@end

Ответ 3

Старый подход с добавлением метода show() и локального экземпляра UIWindow больше не работает на iOS 13 (окно сразу закрывается).

Вот расширение UIAlertController Swift, которое должно работать на iOS 13:

import UIKit

private var associationKey: UInt8 = 0

extension UIAlertController {

    private var alertWindow: UIWindow! {
        get {
            return objc_getAssociatedObject(self, &associationKey) as? UIWindow
        }

        set(newValue) {
            objc_setAssociatedObject(self, &associationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
        }
    }

    func show() {
        self.alertWindow = UIWindow.init(frame: UIScreen.main.bounds)
        self.alertWindow.backgroundColor = .red

        let viewController = UIViewController()
        viewController.view.backgroundColor = .green
        self.alertWindow.rootViewController = viewController

        let topWindow = UIApplication.shared.windows.last
        if let topWindow = topWindow {
            self.alertWindow.windowLevel = topWindow.windowLevel + 1
        }

        self.alertWindow.makeKeyAndVisible()
        self.alertWindow.rootViewController?.present(self, animated: true, completion: nil)
    }

    override open func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        self.alertWindow.isHidden = true
        self.alertWindow = nil
    }
}

Такой UIAlertController затем можно создать и показать следующим образом:

let alertController = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert)
let alertAction = UIAlertAction(title: "Title", style: .default) { (action) in
    print("Action")
}

alertController.addAction(alertAction)
alertController.show()

Ответ 4

в Swift 4.1 и Xcode 9.4.1

Я вызываю функцию оповещения из моего общего класса

//This is my shared class
import UIKit

class SharedClass: NSObject {

static let sharedInstance = SharedClass()
    //This is alert function
    func alertWindow(title: String, message: String) {
        let alertWindow = UIWindow(frame: UIScreen.main.bounds)
        alertWindow.rootViewController = UIViewController()
        alertWindow.windowLevel = UIWindowLevelAlert + 1

        let alert2 = UIAlertController(title: title, message: message, preferredStyle: .alert)
        let defaultAction2 = UIAlertAction(title: "OK", style: .default, handler: { action in
        })
        alert2.addAction(defaultAction2)

        alertWindow.makeKeyAndVisible()
        alertWindow.rootViewController?.present(alert2, animated: true, completion: nil)
    }
    private override init() {
    }
}

Я вызываю эту функцию оповещения в моем необходимом контроллере представления следующим образом.

//I'm calling this function into my second view controller
SharedClass.sharedInstance.alertWindow(title:"Title message here", message:"Description message here")

Ответ 5

Пример Swift 3

let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1

let alert = UIAlertController(title: "AlertController Tutorial", message: "Submit something", preferredStyle: .alert)

alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: true, completion: nil)

Ответ 6

Часто цитируемое решение, использующее недавно созданный UIWindow в качестве расширения UIAlertController, перестало работать в iOS 13 Betas (похоже, что iOS больше не ссылается на UIWindow, поэтому предупреждение немедленно исчезает).

Приведенное ниже решение немного сложнее, но работает в iOS 13.0 и более старых версиях iOS:

class GBViewController: UIViewController {
    var didDismiss: (() -> Void)?
    override func dismiss(animated flag: Bool, completion: (() -> Void)?)
    {
        super.dismiss(animated: flag, completion:completion)
        didDismiss?()
    }
    override var prefersStatusBarHidden: Bool {
        return true
    }
}

class GlobalPresenter {
    var globalWindow: UIWindow?
    static let shared = GlobalPresenter()

    private init() {
    }

    func present(controller: UIViewController) {
        globalWindow = UIWindow(frame: UIScreen.main.bounds)
        let root = GBViewController()
        root.didDismiss = {
            self.globalWindow?.resignKey()
            self.globalWindow = nil
        }
        globalWindow!.rootViewController = root
        globalWindow!.windowLevel = UIWindow.Level.alert + 1
        globalWindow!.makeKeyAndVisible()
        globalWindow!.rootViewController?.present(controller, animated: true, completion: nil)
    }
}

Usage

Usage
    let alert = UIAlertController(title: "Alert Test", message: "Alert!", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
    GlobalPresenter.shared.present(controller: alert)

Ответ 7

 func windowErrorAlert(message:String){
    let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
    let window = UIWindow(frame: UIScreen.main.bounds)
    window.rootViewController = UIViewController()
    let okAction = UIAlertAction(title: "Ok", style: .default) { (action) -> Void in
        alert.dismiss(animated: true, completion: nil)
        window.resignKey()
        window.isHidden = true
        window.removeFromSuperview()
        window.windowLevel = UIWindowLevelAlert - 1
        window.setNeedsLayout()
    }

    alert.addAction(okAction)
    window.windowLevel = UIWindowLevelAlert + 1
    window.makeKeyAndVisible()

    window.rootViewController?.present(alert, animated: true, completion: nil)
}

Создайте UIAlertController поверх всего представления, а также отклоните и верните фокус на ваш rootViewController.

Ответ 8

Мой собственный обход iOS 13.

Вместо того, чтобы создавать подклассы UIViewController (решение Andreas) или добавлять связанный объект (решение Maxim), я использую очень простой подкласс UIWindow, объявленный в файле категории (расширения) UIAlertController:

@interface AlertWindow : UIWindow
@property (nonatomic) UIWindow *autoretain;
@end

@implementation AlertWindow
@end

Затем в методе шоу:

AlertWindow *win = [[AlertWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
win.autoretain = win;
… //code as usual

и, наконец, выпустить:

- (void)viewWillDisappear:(BOOL)animated {
    AlertWindow *win = (AlertWindow *)self.view.window;
    if ([win isKindOfClass:AlertWindow.class]) win.autoretain = nil;
    [super viewWillDisappear:animated];
}

Я понимаю, что ОП помечен как Swift, а это ObjC, но это так просто адаптировать...