ios - 无论视图层次结构如何,都在所有内容之上显示 UIAlertController
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/40991450/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
ios - present UIAlertController on top of everything regardless of the view hierarchy
提问by Guig
I'm trying to have an helper class that presents an UIAlertController
. Since it's a helper class, I want it to work regardless of the view hierarchy, and with no information about it. I'm able to show the alert, but when it's being dismissed, the app crashed with:
我正在尝试创建一个提供UIAlertController
. 因为它是一个辅助类,所以我希望它在不考虑视图层次结构的情况下工作,并且没有关于它的信息。我能够显示警报,但是当它被关闭时,应用程序崩溃了:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Trying to dismiss UIAlertController <UIAlertController: 0x135d70d80>
with unknown presenter.'
I'm creating the popup with:
我正在创建弹出窗口:
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 ...
When I tap yes
, the app will crash and the handler is not being hit before the crash. I can't present the UIAlertController
because this would be dependent of the current view hierarchy, while I want the popup to be independant
当我点击 时yes
,应用程序将崩溃并且处理程序在崩溃之前不会被击中。我无法呈现,UIAlertController
因为这将取决于当前的视图层次结构,而我希望弹出窗口是独立的
EDIT: Swift solution Thanks @Vlad for the idea. It seems that operating in a separate window is much more simple. So here is a working Swift solution:
编辑:Swift 解决方案感谢@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)
}
}
回答by jazzgil
Update Dec 16, 2019:
2019 年 12 月 16 日更新:
Just present the view controller/alert from the current top-most view controller. That will work :)
只需从当前最顶层的视图控制器呈现视图控制器/警报。那可行 :)
if #available(iOS 13.0, *) {
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
topController.present(self, animated: true, completion: nil)
}
Update July 23, 2019:
2019 年 7 月 23 日更新:
IMPORTANT
重要的
Apparently the method below this technique stopped working in iOS 13.0:(
显然,此技术下面的方法在 iOS 13.0 中停止工作:(
I'll update once I find the time to investigate...
一旦我找到时间调查,我会更新......
Old technique:
旧技术:
Here's a Swift (5) extension for it:
这是它的 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)
}
}
Just setup your UIAlertController, and then call:
只需设置您的 UIAlertController,然后调用:
alert.show()
No more bound by the View Controllers hierarchy!
不再受 View Controllers 层次结构的束缚!
回答by Vladyslav Zavalykhatko
I will rather present it on UIApplication.shared.keyWindow.rootViewController, instead of using your logic. So you can do next:
我宁愿将它呈现在 UIApplication.shared.keyWindow.rootViewController 上,而不是使用您的逻辑。所以你可以做下一步:
UIApplication.shared.keyWindow.rootViewController.presentController(yourAlert, animated: true, completion: nil)
EDITED:
编辑:
I have an old ObjC category, where I've used the next method show, which I used, if no controller was provided to present from:
我有一个旧的 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];
}
added entire category, if somebody need it
添加整个类别,如果有人需要它
#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
回答by Maxim Makhun
Old approach with adding show()
method and local instance of UIWindow
no longer works on iOS 13 (window is dismissed right away).
添加show()
方法和本地实例的旧方法UIWindow
不再适用于 iOS 13(窗口立即关闭)。
Here is UIAlertController
Swift extension which should work on iOS 13:
这是适用于 iOS 13 的UIAlertController
Swift 扩展:
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
}
}
Such UIAlertController
then can be created and shown like this:
这样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()
回答by iOS
in Swift 4.1 and Xcode 9.4.1
在 Swift 4.1 和 Xcode 9.4.1 中
I'm calling alert function from my shared class
我正在从我的共享类调用警报功能
//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 alert function in my required view controller like this.
我正在像这样在我需要的视图控制器中调用这个警报函数。
//I'm calling this function into my second view controller
SharedClass.sharedInstance.alertWindow(title:"Title message here", message:"Description message here")
回答by AndreasB
The often cited solution using a newly created UIWindow
as a UIAlertController
extension stopped working in iOS 13 Betas (looks like there is no strong reference held by iOS to the UIWindow
anymore, so the alert disappears immediately).
使用新创建UIWindow
的UIAlertController
扩展程序经常被引用的解决方案在 iOS 13 Betas 中停止工作(看起来 iOSUIWindow
不再拥有对 的强引用,因此警报立即消失)。
The below solution is slightly more complex, but works in iOS 13.0 and older versions of iOS:
下面的解决方案稍微复杂一些,但适用于 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
用法
let alert = UIAlertController(title: "Alert Test", message: "Alert!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
GlobalPresenter.shared.present(controller: alert)
回答by Lijith Vipin
Swift 3example
斯威夫特 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)
回答by Max_B
My own iOS 13 workaround.
我自己的 iOS 13 解决方法。
Edit notice: I edited my previous answer because, as others solutions around, it used a redefinition of viewWillDisappear:
which is incorrect in a class extension and effectively stoped working with 13.4.
编辑通知:我编辑了我之前的答案,因为与周围的其他解决方案一样,它viewWillDisappear:
在类扩展中使用了不正确的重新定义,并有效地停止了使用 13.4。
This solution, based on UIWindow paradigm, defines a category (extension) on UIAlertController
. In that category file we also define a simple subclass of UIViewController
used to present theUIAlertController
.
该解决方案基于 UIWindow 范式,在UIAlertController
. 在那个类别文件中,我们还定义了一个简单的子类,UIViewController
用于呈现UIAlertController
.
@interface AlertViewControllerPresenter : UIViewController
@property UIWindow *win;
@end
@implementation AlertViewControllerPresenter
- (void) dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
[_win resignKeyWindow]; //optional nilling the window works
_win.hidden = YES; //optional nilling the window works
_win = nil;
[super dismissViewControllerAnimated:flag completion:completion];
}
@end
The presenter retains the window. When the presented alert is dismissed the window is released.
演示者保留窗口。当呈现的警报被解除时,窗口被释放。
Then define a show method in the category (extension) :
然后在类别(扩展)中定义一个 show 方法:
- (void)show {
AlertViewControllerPresenter *vc = [[AlertViewControllerPresenter alloc] init];
vc.view.backgroundColor = UIColor.clearColor;
UIWindow *win = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
vc.win = win;
win.rootViewController = vc;
win.windowLevel = UIWindowLevelAlert;
[win makeKeyAndVisible];
[vc presentViewController:self animated:YES completion:nil];
}
I do realise that the OP tagged Swift and this is ObjC, but this is so straightforward to adapt…
我确实意识到 OP 标记了 Swift,这是 ObjC,但这很容易适应……
回答by Ruslan Sabirov
working solution for TVOS 13 and iOS 13
TVOS 13 和 iOS 13 的工作解决方案
static func showOverAnyVC(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
alert.addAction((UIAlertAction(title: "OK", style: .default, handler: {(action) -> Void in
})))
let appDelegate = UIApplication.shared.delegate as! AppDelegate
var topController: UIViewController = appDelegate.window!.rootViewController!
while (topController.presentedViewController != nil) {
topController = topController.presentedViewController!
}
topController.present(alert, animated: true, completion: nil)
}
回答by anuraagdjain
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)
}
Create a UIAlertController on top of all view and also dismiss and give focus back to your rootViewController.
在所有视图之上创建一个 UIAlertController 并关闭并将焦点返回给您的 rootViewController。