Как представить UIAlertController, когда он не находится в контроллере вида?
сценарий: пользователь нажимает на кнопку на контроллере вид. Контроллер вида является самым верхним (очевидно) в стеке навигации. Кран вызывает метод служебного класса, вызываемый для другого класса. Там происходит что-то плохое, и я хочу отобразить предупреждение прямо там, прежде чем управление вернется к контроллеру вида.
+ (void)myUtilityMethod {
// do stuff
// something bad happened, display an alert.
}
Это было возможно с UIAlertView
(но, возможно, не совсем правильное).
в этом случае, как вы представить UIAlertController
, тут же в myUtilityMethod
?
30 ответов:
в WWDC я зашел в одну из лабораторий и задал инженеру Apple тот же вопрос: "какая была лучшая практика для отображения
UIAlertController
?"И он сказал, что они часто задавали этот вопрос, и мы шутили, что они должны были провести сеанс по этому вопросу. Он сказал, что внутренне Apple создаетUIWindow
прозрачныйUIViewController
а затем представляяUIAlertController
на нем. В основном то, что находится в ответе Дилана Беттермана.но я не хотел использовать подкласс
UIAlertController
потому что это потребует от меня изменения моего кода во всем моем приложении. Поэтому с помощью связанного объекта я сделал категорию наUIAlertController
обеспечиваетshow
метод в Objective-С.вот соответствующий код:
#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];
The
UIWindow
это будет уничтожено, когдаUIAlertController
освобождается, так как это единственный объект, который сохраняетUIWindow
. Но если вы назначаетеUIAlertController
в свойство или вызвать его сохранить счетчик для увеличения путем доступа к оповещению в одном из блоков действий,UIWindow
останется на экране, блокируя ваш пользовательский интерфейс. Смотрите пример кода использования выше, чтобы избежать в случае необходимости доступаUITextField
.я сделал РЕПО GitHub с тестовым проектом:FFGlobalAlertController
Вы можете сделать следующее С Swift 2.2:
let alertController: UIAlertController = ... UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)
И Swift 3.0:
let alertController: UIAlertController = ... UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
С
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.3
let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .Alert) //... var rootViewController = UIApplication.sharedApplication().keyWindow?.rootViewController if let navigationController = rootViewController as? UINavigationController { rootViewController = navigationController.viewControllers.first } if let tabBarController = rootViewController as? UITabBarController { rootViewController = tabBarController.selectedViewController } rootViewController?.presentViewController(alertController, animated: true, completion: nil)
Swift 3
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)
общих
UIAlertController
extension
для всех случаевUINavigationController
и/илиUITabBarController
. Также работает, если есть модальный VC на экране в данный момент.использование:
//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) } } } }
Я написал аналогичный вопрос пару месяцев назад и думаю, что я наконец решил проблему. Перейдите по ссылке в нижней части моего поста, если вы просто хотите увидеть код.
решение заключается в использовании дополнительного UIWindow.
Если вы хотите отобразить ваш UIAlertController:
- Сделайте ваше окно ключом и видимым окном (
window.makeKeyAndVisible()
)- просто используйте простой экземпляр UIViewController в качестве rootViewController новое окно. (
window.rootViewController = UIViewController()
)- представьте Ваш UIAlertController на rootViewController вашего окна
Примечание:
- на ваше UIWindow должна быть сильная ссылка. Если на него нет сильной ссылки, он никогда не появится (потому что он выпущен). Я рекомендую использовать свойство, но я также имел успех с Связанный объект.
- чтобы убедиться, что окно отображается выше всего остального (в том числе system UIAlertControllers), я установил уровень окна. (
window.windowLevel = UIWindowLevelAlert + 1
)наконец, у меня есть завершенная реализация, если вы просто хотите посмотреть на это.
улучшение ответ 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];
следующее решение сделал не работа, хотя это выглядело довольно многообещающим со всеми версиями. это решение генерирует предупреждение.
предупреждение: попытка представить на чье мнение не в окне иерархии!
https://stackoverflow.com/a/34487871/2369867 => Тогда это выглядело многообещающе. Но это было не на
Swift 3
. Так я отвечаю на это в Swift 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.
это работает в Swift для обычных контроллеров вида и даже если есть навигационный контроллер на экране:
let alert = UIAlertController(...) let alertWindow = UIWindow(frame: UIScreen.main.bounds) alertWindow.rootViewController = UIViewController() alertWindow.windowLevel = UIWindowLevelAlert + 1; alertWindow.makeKeyAndVisible() alertWindow.rootViewController?.presentViewController(alert, animated: true, completion: nil)
здесь ответ mythicalcoder как расширение, протестировано и работает в Swift 4:
extension UIAlertController { func presentInOwnWindow(animated: Bool, completion: (() -> Void)?) { let alertWindow = UIWindow(frame: UIScreen.main.bounds) alertWindow.rootViewController = UIViewController() alertWindow.windowLevel = UIWindowLevelAlert + 1; alertWindow.makeKeyAndVisible() alertWindow.rootViewController?.present(self, animated: animated, completion: completion) } }
пример использования:
let alertController = UIAlertController(title: "<Alert Title>", message: "<Alert Message>", preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil)) alertController.presentInOwnWindow(animated: true, completion: { print("completed") })
добавляя к ответу Zev (и переключаясь обратно на Objective-C), вы можете столкнуться с ситуацией, когда ваш корневой контроллер представления представляет какой-то другой VC через segue или что-то еще. Вызов presentedViewController в корневом VC позаботится об этом:
[[UIApplication sharedApplication].keyWindow.rootViewController.presentedViewController presentViewController:alertController animated:YES completion:^{}];
это исправило проблему, которую я имел, когда корневой VC перешел на другой VC, и вместо представления контроллера оповещения было выпущено предупреждение, подобное тем, о которых сообщалось выше:
Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!
I не тестировал его, но это также может быть необходимо, если ваш корневой VC является навигационным контроллером.
создать расширение, как в Aviel Gross answer. Здесь у вас есть расширение Objective-C.
здесь у вас есть заголовочный файл *.h
// UIAlertController+Showable.h #import <UIKit/UIKit.h> @interface UIAlertController (Showable) - (void)show; - (void)presentAnimated:(BOOL)animated completion:(void (^)(void))completion; - (void)presentFromController:(UIViewController *)viewController animated:(BOOL)animated completion:(void (^)(void))completion; @end
и реализации: *.м
// UIAlertController+Showable.m #import "UIAlertController+Showable.h" @implementation UIAlertController (Showable) - (void)show { [self presentAnimated:YES completion:nil]; } - (void)presentAnimated:(BOOL)animated completion:(void (^)(void))completion { UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController; if (rootVC != nil) { [self presentFromController:rootVC animated:animated completion:completion]; } } - (void)presentFromController:(UIViewController *)viewController animated:(BOOL)animated completion:(void (^)(void))completion { if ([viewController isKindOfClass:[UINavigationController class]]) { UIViewController *visibleVC = ((UINavigationController *)viewController).visibleViewController; [self presentFromController:visibleVC animated:animated completion:completion]; } else if ([viewController isKindOfClass:[UITabBarController class]]) { UIViewController *selectedVC = ((UITabBarController *)viewController).selectedViewController; [self presentFromController:selectedVC animated:animated completion:completion]; } else { [viewController presentViewController:self animated:animated completion:completion]; } } @end
вы используете это расширение в файле реализации следующим образом:
#import "UIAlertController+Showable.h" UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Title here" message:@"Detail message here" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}]; [alert addAction:defaultAction]; // Add more actions if needed [alert show];
ответ@agilityvision переведен на Swift4 / iOS11. Я не использовал локализованные строки, но вы можете легко изменить это:
import UIKit /** An alert controller that can be called without a view controller. Creates a blank view controller and presents itself over that **/ class AlertPlusViewController: UIAlertController { private var alertWindow: UIWindow? override func viewDidLoad() { super.viewDidLoad() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.alertWindow?.isHidden = true alertWindow = nil } func show() { self.showAnimated(animated: true) } func showAnimated(animated _: Bool) { let blankViewController = UIViewController() blankViewController.view.backgroundColor = UIColor.clear let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = blankViewController window.backgroundColor = UIColor.clear window.windowLevel = UIWindowLevelAlert + 1 window.makeKeyAndVisible() self.alertWindow = window blankViewController.present(self, animated: true, completion: nil) } func presentOkayAlertWithTitle(title: String?, message: String?) { let alertController = AlertPlusViewController(title: title, message: message, preferredStyle: .alert) let okayAction = UIAlertAction(title: "Ok", style: .default, handler: nil) alertController.addAction(okayAction) alertController.show() } func presentOkayAlertWithError(error: NSError?) { let title = "Error" let message = error?.localizedDescription presentOkayAlertWithTitle(title: title, message: message) } }
кросс-пост моего ответ так как эти два потока не помечены как обманы...
теперь
UIViewController
входит в сеть ответчика, вы можете сделать что-то вроде этого:if let vc = self.nextResponder()?.targetForAction(#selector(UIViewController.presentViewController(_:animated:completion:)), withSender: self) as? UIViewController { let alert = UIAlertController(title: "A snappy title", message: "Something bad happened", preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil)) vc.presentViewController(alert, animated: true, completion: nil) }
ответ зева Айзенберга прост и понятен, но он не всегда работает, и он может потерпеть неудачу с этим предупреждающим сообщением:
Warning: Attempt to present <UIAlertController: 0x7fe6fd951e10> on <ThisViewController: 0x7fe6fb409480> which is already presenting <AnotherViewController: 0x7fe6fd109c00>
это связано с тем, что windows rootViewController не находится в верхней части представленных представлений. Чтобы исправить это, нам нужно пройти вверх по цепочке презентаций, как показано в моем коде расширения UIAlertController, написанном на Swift 3:
/// show the alert in a view controller if specified; otherwise show from window's root pree func show(inViewController: UIViewController?) { if let vc = inViewController { vc.present(self, animated: true, completion: nil) } else { // find the root, then walk up the chain var viewController = UIApplication.shared.keyWindow?.rootViewController var presentedVC = viewController?.presentedViewController while presentedVC != nil { viewController = presentedVC presentedVC = viewController?.presentedViewController } // now we present viewController?.present(self, animated: true, completion: nil) } } func show() { show(inViewController: nil) }
обновления на 15.09.2017:
проверено и подтверждено, что выше Логика по-прежнему отлично работает в недавно доступной iOS 11 GM seed. Верхний проголосовал метод agilityvision, однако, не делает: предупреждающий вид, представленный в новоиспеченном
UIWindow
ниже клавиатуры и потенциально предотвращает нажатие кнопки. Это связано с тем, что в iOS 11 все оконные уровни выше, чем у окна клавиатуры, опускаются до уровня ниже его.один артефакт представления от
keyWindow
хотя это анимация клавиатуры скольжения вниз, когда предупреждение представленный, и сползать вверх снова когда сигнал тревоги уволен. Если вы хотите, чтобы клавиатура оставалась там во время презентации, вы можете попробовать представить ее из самого верхнего окна, как показано в приведенном ниже коде:func show(inViewController: UIViewController?) { if let vc = inViewController { vc.present(self, animated: true, completion: nil) } else { // get a "solid" window with the highest level let alertWindow = UIApplication.shared.windows.filter { .tintColor != nil || .className() == "UIRemoteKeyboardWindow" }.sorted(by: { (w1, w2) -> Bool in return w1.windowLevel < w2.windowLevel }).last // save the top window's tint color let savedTintColor = alertWindow?.tintColor alertWindow?.tintColor = UIApplication.shared.keyWindow?.tintColor // walk up the presentation tree var viewController = alertWindow?.rootViewController while viewController?.presentedViewController != nil { viewController = viewController?.presentedViewController } viewController?.present(self, animated: true, completion: nil) // restore the top window's tint color if let tintColor = savedTintColor { alertWindow?.tintColor = tintColor } } }
единственная не столь большая часть приведенного выше кода заключается в том, что он проверяет имя класса
UIRemoteKeyboardWindow
чтобы убедиться, что мы можем включить его тоже. Тем не менее, приведенный выше код отлично работает в iOS 9, 10 и 11 GM seed, с правильным оттенком цвета и без артефактов скольжения клавиатуры.
стенографический способ представить предупреждение в Objective-C:
[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:alertController animated:YES completion:nil];
здесь
alertController
ваш
extension UIApplication { /// The top most view controller static var topMostViewController: UIViewController? { return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController } } extension UIViewController { /// The visible view controller from a given view controller var visibleViewController: UIViewController? { if let navigationController = self as? UINavigationController { return navigationController.topViewController?.visibleViewController } else if let tabBarController = self as? UITabBarController { return tabBarController.selectedViewController?.visibleViewController } else if let presentedViewController = presentedViewController { return presentedViewController.visibleViewController } else { return self } } }
С этим вы можете легко представить свое предупреждение так
UIApplication.topMostViewController?.present(viewController, animated: true, completion: nil)
одна вещь, чтобы отметить, что если есть UIAlertController в настоящее время отображается
UIApplication.topMostViewController
вернет aUIAlertController
. Представляя на вершинеUIAlertController
имеет странное поведение, и их следует избегать. Таким образом, вы должны либо вручную проверить, что!(UIApplication.topMostViewController is UIAlertController)
перед представлением, или добавитьelse if
case для возврата nil ifself is UIAlertController
extension UIViewController { /// The visible view controller from a given view controller var visibleViewController: UIViewController? { if let navigationController = self as? UINavigationController { return navigationController.topViewController?.visibleViewController } else if let tabBarController = self as? UITabBarController { return tabBarController.selectedViewController?.visibleViewController } else if let presentedViewController = presentedViewController { return presentedViewController.visibleViewController } else if self is UIAlertController { return nil } else { return self } } }
Swift 4+
решение я использую в течение многих лет без проблем вообще. Прежде всего я расширяю
UIWindow
чтобы найти его visibleViewController. Примечание: если вы используете пользовательские коллекции* классы (например, боковое меню) вы должны добавить обработчик для этого случая в следующем расширении. После получения самого верхнего контроллера вида его легко представитьUIAlertController
какUIAlertView
.extension UIAlertController { func show(animated: Bool = true, completion: (() -> Void)? = nil) { if let visibleViewController = UIApplication.shared.keyWindow?.visibleViewController { visibleViewController.present(self, animated: animated, completion: completion) } } } extension UIWindow { var visibleViewController: UIViewController? { guard let rootViewController = rootViewController else { return nil } return visibleViewController(for: rootViewController) } private func visibleViewController(for controller: UIViewController) -> UIViewController { var nextOnStackViewController: UIViewController? = nil if let presented = controller.presentedViewController { nextOnStackViewController = presented } else if let navigationController = controller as? UINavigationController, let visible = navigationController.visibleViewController { nextOnStackViewController = visible } else if let tabBarController = controller as? UITabBarController, let visible = (tabBarController.selectedViewController ?? tabBarController.presentedViewController) { nextOnStackViewController = visible } if let nextOnStackViewController = nextOnStackViewController { return visibleViewController(for: nextOnStackViewController) } else { return controller } } }
вы можете отправить текущее представление или контроллер в качестве параметра:
+ (void)myUtilityMethod:(id)controller { // do stuff // something bad happened, display an alert. }
в дополнение к отличным ответам (agilityvision,Адиб,malhal). Чтобы достичь поведения очереди, как в старых добрых UIAlertViews (избегайте перекрытия окон предупреждений), используйте этот блок для наблюдения за доступностью уровня окна:
@interface UIWindow (WLWindowLevel) + (void)notifyWindowLevelIsAvailable:(UIWindowLevel)level withBlock:(void (^)())block; @end @implementation UIWindow (WLWindowLevel) + (void)notifyWindowLevelIsAvailable:(UIWindowLevel)level withBlock:(void (^)())block { UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; if (keyWindow.windowLevel == level) { // window level is occupied, listen for windows to hide id observer; observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIWindowDidBecomeHiddenNotification object:keyWindow queue:nil usingBlock:^(NSNotification *note) { [[NSNotificationCenter defaultCenter] removeObserver:observer]; [self notifyWindowLevelIsAvailable:level withBlock:block]; // recursive retry }]; } else { block(); // window level is available } } @end
пример:
[UIWindow notifyWindowLevelIsAvailable:UIWindowLevelAlert withBlock:^{ UIWindow *alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; alertWindow.windowLevel = UIWindowLevelAlert; alertWindow.rootViewController = [UIViewController new]; [alertWindow makeKeyAndVisible]; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:nil preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { alertWindow.hidden = YES; }]]; [alertWindow.rootViewController presentViewController:alertController animated:YES completion:nil]; }];
Это позволит вам избежать перекрытия окон оповещения. Один и тот же метод можно использовать для разделения и ввода контроллеров представления очереди для любого количества окон слои.
Если кто-то заинтересован я создал Swift 3 версия @agilityvision ответ. Код:
import Foundation import UIKit extension UIAlertController { var window: UIWindow? { get { return objc_getAssociatedObject(self, "window") as? UIWindow } set { objc_setAssociatedObject(self, "window", newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } open override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.window?.isHidden = true self.window = nil } func show(animated: Bool = true) { let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UIViewController(nibName: nil, bundle: nil) let delegate = UIApplication.shared.delegate if delegate?.window != nil { window.tintColor = delegate!.window!!.tintColor } window.windowLevel = UIApplication.shared.windows.last!.windowLevel + 1 window.makeKeyAndVisible() window.rootViewController!.present(self, animated: animated, completion: nil) self.window = window } }
вы можете попробовать реализовать категорию на
UIViewController
с метод, как- (void)presentErrorMessage;
и внутри этого метода вы реализуете UIAlertController, а затем представить его наself
. Чем в вашем клиентском коде вы будете иметь что-то вроде:
[myViewController presentErrorMessage];
таким образом, вы избежите ненужных параметров и предупреждений о том, что представление не находится в иерархии окон.
там 2 подхода, которые вы можете использовать:
-используйте
UIAlertView
или' UIActionSheet ' вместо этого (не рекомендуется, потому что он устарел в iOS 8, но теперь он работает)-как-то помните последний контроллер вида, который представлен. Вот пример.
@interface UIViewController (TopController) + (UIViewController *)topViewController; @end // implementation #import "UIViewController+TopController.h" #import <objc/runtime.h> static __weak UIViewController *_topViewController = nil; @implementation UIViewController (TopController) + (UIViewController *)topViewController { UIViewController *vc = _topViewController; while (vc.parentViewController) { vc = vc.parentViewController; } return vc; } + (void)load { [super load]; [self swizzleSelector:@selector(viewDidAppear:) withSelector:@selector(myViewDidAppear:)]; [self swizzleSelector:@selector(viewWillDisappear:) withSelector:@selector(myViewWillDisappear:)]; } - (void)myViewDidAppear:(BOOL)animated { if (_topViewController == nil) { _topViewController = self; } [self myViewDidAppear:animated]; } - (void)myViewWillDisappear:(BOOL)animated { if (_topViewController == self) { _topViewController = nil; } [self myViewWillDisappear:animated]; } + (void)swizzleSelector:(SEL)sel1 withSelector:(SEL)sel2 { Class class = [self class]; Method originalMethod = class_getInstanceMethod(class, sel1); Method swizzledMethod = class_getInstanceMethod(class, sel2); BOOL didAddMethod = class_addMethod(class, sel1, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, sel2, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } @end
использование:
[[UIViewController topViewController] presentViewController:alertController ...];
Кевин Слич предоставил отличное решение.
теперь я использую приведенный ниже код в моем основном подклассе UIViewController.
одно небольшое изменение я сделал, чтобы проверить, если лучший контроллер презентации не является простым UIViewController. Если нет, то это должен быть какой-то VC, который представляет собой простой VC. Таким образом, мы возвращаем VC, который представлен вместо этого.
- (UIViewController *)bestPresentationController { UIViewController *bestPresentationController = [UIApplication sharedApplication].keyWindow.rootViewController; if (![bestPresentationController isMemberOfClass:[UIViewController class]]) { bestPresentationController = bestPresentationController.presentedViewController; } return bestPresentationController; }
Кажется, все работает до сих пор в моем тестировании.
спасибо Кевин!
Я использую этот код с некоторыми небольшими личными вариациями в моем классе AppDelegate
-(UIViewController*)presentingRootViewController { UIViewController *vc = self.window.rootViewController; if ([vc isKindOfClass:[UINavigationController class]] || [vc isKindOfClass:[UITabBarController class]]) { // filter nav controller vc = [AppDelegate findChildThatIsNotNavController:vc]; // filter tab controller if ([vc isKindOfClass:[UITabBarController class]]) { UITabBarController *tbc = ((UITabBarController*)vc); if ([tbc viewControllers].count > 0) { vc = [tbc viewControllers][tbc.selectedIndex]; // filter nav controller again vc = [AppDelegate findChildThatIsNotNavController:vc]; } } } return vc; } /** * Private helper */ +(UIViewController*)findChildThatIsNotNavController:(UIViewController*)vc { if ([vc isKindOfClass:[UINavigationController class]]) { if (((UINavigationController *)vc).viewControllers.count > 0) { vc = [((UINavigationController *)vc).viewControllers objectAtIndex:0]; } } return vc; }
Кажется, работает:
static UIViewController *viewControllerForView(UIView *view) { UIResponder *responder = view; do { responder = [responder nextResponder]; } while (responder && ![responder isKindOfClass:[UIViewController class]]); return (UIViewController *)responder; } -(void)showActionSheet { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; [alertController addAction:[UIAlertAction actionWithTitle:@"Do it" style:UIAlertActionStyleDefault handler:nil]]; [viewControllerForView(self) presentViewController:alertController animated:YES completion:nil]; }
создать вспомогательный класс AlertWindow и чем использовать как
let alertWindow = AlertWindow(); let alert = UIAlertController(title: "Hello", message: "message", preferredStyle: .alert); let cancel = UIAlertAction(title: "Ok", style: .cancel){(action) in //.... action code here // reference to alertWindow retain it. Every action must have this at end alertWindow.isHidden = true; // here AlertWindow.deinit{ } } alert.addAction(cancel); alertWindow.present(alert, animated: true, completion: nil) class AlertWindow:UIWindow{ convenience init(){ self.init(frame:UIScreen.main.bounds); } override init(frame: CGRect) { super.init(frame: frame); if let color = UIApplication.shared.delegate?.window??.tintColor { tintColor = color; } rootViewController = UIViewController() windowLevel = UIWindowLevelAlert + 1; makeKeyAndVisible() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit{ // semaphor.signal(); } func present(_ ctrl:UIViewController, animated:Bool, completion: (()->Void)?){ rootViewController!.present(ctrl, animated: animated, completion: completion); } }
@agilityvision ответ настолько хорош. У меня есть смысл использовать в проектах swift, поэтому я подумал, что поделюсь своим мнением о его ответе с помощью swift 3.0
fileprivate class MyUIAlertController: UIAlertController { typealias Handler = () -> Void struct AssociatedKeys { static var alertWindowKey = "alertWindowKey" } dynamic var _alertWindow: UIWindow? var alertWindow: UIWindow? { return objc_getAssociatedObject(self, &AssociatedKeys.alertWindowKey) as? UIWindow } func setAlert(inWindow window: UIWindow) { objc_setAssociatedObject(self, &AssociatedKeys.alertWindowKey, _alertWindow, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } func show(completion: Handler? = nil) { show(animated: true, completion: completion) } func show(animated: Bool, completion: Handler? = nil) { _alertWindow = UIWindow(frame: UIScreen.main.bounds) _alertWindow?.rootViewController = UIViewController() if let delegate: UIApplicationDelegate = UIApplication.shared.delegate, let window = delegate.window { _alertWindow?.tintColor = window?.tintColor } let topWindow = UIApplication.shared.windows.last _alertWindow?.windowLevel = topWindow?.windowLevel ?? 0 + 1 _alertWindow?.makeKeyAndVisible() _alertWindow?.rootViewController?.present(self, animated: animated, completion: completion) } fileprivate override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) _alertWindow?.isHidden = true _alertWindow = nil } }
Я пробовал все, что упоминалось, но без успеха. Метод, который я использовал для Swift 3.0 :
extension UIAlertController { func show() { present(animated: true, completion: nil) } func present(animated: Bool, completion: (() -> Void)?) { if var topController = UIApplication.shared.keyWindow?.rootViewController { while let presentedViewController = topController.presentedViewController { topController = presentedViewController } topController.present(self, animated: animated, completion: completion) } } }
В Swift 3
let alertLogin = UIAlertController.init(title: "Your Title", message:"Your message", preferredStyle: .alert) alertLogin.addAction(UIAlertAction(title: "Done", style:.default, handler: { (AlertAction) in })) self.window?.rootViewController?.present(alertLogin, animated: true, completion: nil)