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)
}
}
Ответы
Ответ 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, но это так просто адаптировать...