Làm thế nào để trình bày UIAlertContoder khi không có trong bộ điều khiển xem?


255

Kịch bản: Người dùng chạm vào một nút trên bộ điều khiển xem. Bộ điều khiển xem là trên cùng (rõ ràng) trong ngăn xếp điều hướng. Vòi gọi một phương thức lớp tiện ích được gọi trên một lớp khác. Một điều tồi tệ xảy ra ở đó và tôi muốn hiển thị một cảnh báo ngay tại đó trước khi điều khiển quay trở lại bộ điều khiển xem.

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

Điều này là có thể với UIAlertView(nhưng có lẽ không hoàn toàn đúng).

Trong trường hợp này, làm thế nào để bạn trình bày một UIAlertController, ngay trong đó myUtilityMethod?

Câu trả lời:


34

Tôi đã đăng một câu hỏi tương tự một vài tháng trước và nghĩ rằng cuối cùng tôi đã giải quyết được vấn đề. Theo liên kết ở dưới cùng của bài viết của tôi nếu bạn chỉ muốn xem mã.

Giải pháp là sử dụng UIWindow bổ sung.

Khi bạn muốn hiển thị UIAlertControll của mình:

  1. Đặt cửa sổ của bạn làm chìa khóa và cửa sổ hiển thị ( window.makeKeyAndVisible())
  2. Chỉ cần sử dụng một cá thể UIViewControll đơn giản làm rootViewContaptor của cửa sổ mới. ( window.rootViewController = UIViewController())
  3. Trình bày UIAlertControll của bạn trên rootViewControll của cửa sổ

Một vài điều cần lưu ý:

  • UIWindow của bạn phải được tham chiếu mạnh mẽ. Nếu nó không được tham chiếu mạnh mẽ, nó sẽ không bao giờ xuất hiện (vì nó được phát hành). Tôi khuyên bạn nên sử dụng một tài sản, nhưng tôi cũng đã thành công với một đối tượng liên quan .
  • Để đảm bảo rằng cửa sổ xuất hiện bên trên mọi thứ khác (bao gồm cả hệ thống UIAlertControllers), tôi đặt windowLevel. ( window.windowLevel = UIWindowLevelAlert + 1)

Cuối cùng, tôi có một triển khai hoàn thành nếu bạn chỉ muốn nhìn vào đó.

https://github.com/dbettermann/DBAlertCont điều khiển


Bạn không có cái này cho Objective-C, phải không?
SAH

2
Có, nó thậm chí hoạt động trong Swift 2.0 / iOS 9. Tôi đang làm việc trên phiên bản Objective-C ngay bây giờ vì có người khác yêu cầu (có thể đó là bạn). Tôi sẽ gửi lại khi tôi hoàn thành.
Dylan Bettermann

322

Tại WWDC, tôi dừng lại ở một trong các phòng thí nghiệm và hỏi một Kỹ sư Apple câu hỏi tương tự: "Cách thực hành tốt nhất để hiển thị là UIAlertControllergì?" Và anh ấy nói rằng họ đã nhận được câu hỏi này rất nhiều và chúng tôi đã nói đùa rằng họ nên có một phiên về nó. Ông nói rằng trong nội bộ Apple đang tạo ra một UIWindowsự minh bạchUIViewController và sau đó trình bày UIAlertControllervề nó. Về cơ bản những gì trong câu trả lời của Dylan Betterman.

Nhưng tôi không muốn sử dụng một lớp con UIAlertControllervì điều đó sẽ yêu cầu tôi thay đổi mã trong suốt ứng dụng của mình. Vì vậy, với sự giúp đỡ của một đối tượng liên quan, tôi đã tạo một thể loại trên UIAlertControllerđó cung cấp một showphương thức trong Objective-C.

Đây là mã có liên quan:

#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

Đây là một cách sử dụng mẫu:

// 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];

Cái UIWindowđược tạo sẽ bị hủy khi bị hủy UIAlertController, vì nó là đối tượng duy nhất đang giữ lại UIWindow. Nhưng nếu bạn gán thuộc tính UIAlertControllercho một thuộc tính hoặc làm cho số lượng giữ lại của nó tăng lên bằng cách truy cập cảnh báo vào một trong các khối hành động, thì UIWindownó sẽ ở trên màn hình, khóa giao diện người dùng của bạn. Xem mã sử dụng mẫu ở trên để tránh trong trường hợp cần truy cập UITextField.

Tôi đã tạo một repo GitHub với một dự án thử nghiệm: FFGlobalAlertControll


1
Đồ tốt! Chỉ là một số nền tảng - Tôi đã sử dụng một lớp con thay vì một đối tượng liên quan vì tôi đang sử dụng Swift. Các đối tượng liên kết là một tính năng của thời gian chạy Objective-C và tôi không muốn phụ thuộc vào nó. Swift có lẽ còn nhiều năm nữa mới có được thời gian chạy riêng, nhưng vẫn vậy. :)
Dylan Bettermann

1
Tôi thực sự thích sự tao nhã trong câu trả lời của bạn, tuy nhiên tôi tò mò về cách bạn rút lại cửa sổ mới và làm cho cửa sổ ban đầu trở thành chìa khóa một lần nữa (phải thừa nhận rằng tôi không làm phiền với cửa sổ nhiều).
Dustin Pfannenstiel

1
Cửa sổ khóa là cửa sổ hiển thị trên cùng, vì vậy tôi hiểu là nếu bạn loại bỏ / ẩn cửa sổ "phím", cửa sổ hiển thị tiếp theo xuống sẽ trở thành "phím".
nhanh nhẹn

19
Thực hiện viewDidDisappear:trên một danh mục trông giống như một ý tưởng tồi. Về bản chất, bạn đang cạnh tranh với việc triển khai khung viewDidDisappear:. Cho đến bây giờ có thể ổn, nhưng nếu Apple quyết định thực hiện phương pháp đó trong tương lai, thì không có cách nào để bạn gọi nó (nghĩa là không có điểm tương tự supernào đối với việc triển khai chính phương thức từ triển khai danh mục) .
adib

5
Hoạt động tuyệt vời, nhưng làm thế nào để điều trị prefersStatusBarHiddenpreferredStatusBarStylekhông có thêm một lớp con?
Kevin Flachsmann

109

Nhanh

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)

Mục tiêu-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];

2
+1 Đây là một giải pháp đơn giản tuyệt vời. (Vấn đề tôi gặp phải: Hiển thị một cảnh báo trong DetailViewController của Master / Detail mẫu - Hiển thị trên thiết bị iPad, không bao giờ trên iPhone)
David

8
Thật tuyệt, bạn có thể muốn thêm vào một phần khác: if (rootViewControll.presentsViewControll! = Nil) {rootViewControll = rootViewControll.presentsViewCont điều khiển; }
DivideByZer0

1
Swift 3: 'Alert' đã được đổi tên thành 'alert': let alertControll = UIAlertControll (title: "title", message: "message", preferStyle: .alert)
Kaptain

Sử dụng một đại biểu thay thế!
Andrew Kirna

104

Bạn có thể làm như sau với Swift 2.2:

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

Và Swift 3.0:

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

12
Rất tiếc, tôi đã chấp nhận trước khi tôi kiểm tra. Mã đó trả về bộ điều khiển xem gốc, trong trường hợp của tôi là bộ điều khiển điều hướng. Nó không gây ra lỗi nhưng cảnh báo không hiển thị.
Murray Sagal

22
Và tôi nhận thấy trong giao diện điều khiển : Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!.
Murray Sagal

1
@M bồSagal có bộ điều khiển điều hướng, bạn có thể nhận được visibleViewControllertài sản bất cứ lúc nào để xem bộ điều khiển nào trình bày cảnh báo từ đó. Kiểm tra các tài liệu
Lubo

2
Tôi đã làm điều đó bởi vì tôi không muốn lấy tín dụng cho công việc của người khác. Đó là giải pháp của @ZevEisenberg mà tôi đã sửa đổi cho swift 3.0. Nếu tôi đã thêm một câu trả lời khác thì tôi có thể đã nhận được phiếu bầu mà anh ấy xứng đáng.
jeet.chanchaw

1
Ôi, tôi đã bỏ lỡ tất cả bộ phim ngày hôm qua, nhưng tình cờ tôi vừa cập nhật bài đăng cho Swift 3. Tôi không biết chính sách của SO là gì khi cập nhật câu trả lời cũ cho các phiên bản ngôn ngữ mới, nhưng cá nhân tôi không bận tâm đến nó, miễn là câu trả lời là đúng!
Zev Eisenberg

34

Khá chung UIAlertController extensioncho tất cả các trường hợp UINavigationControllervà / hoặc UITabBarController. Cũng hoạt động nếu có một chế độ VC trên màn hình tại thời điểm này.

Sử dụng:

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

Đây là phần mở rộng:

//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)
          }
        }
    }
}

1
Tôi đang sử dụng giải pháp này và tôi thấy nó thực sự hoàn hảo, thanh lịch, sạch sẽ ... NHƯNG, gần đây tôi đã phải thay đổi bộ điều khiển xem gốc của mình thành một khung nhìn không nằm trong hệ thống phân cấp xem, vì vậy mã này trở nên vô dụng. Bất cứ ai nghĩ về một dix để tiếp tục sử dụng này?

1
Tôi sử dụng một sự kết hợp của giải pháp này với sometinhg khác: Tôi có một singleton UIlớp mà giữ một (yếu!) currentVCKiểu UIViewController.Tôi có BaseViewControllermà thừa hưởng từ UIViewControllervà thiết lập UI.currentVCđể selfvào viewDidAppearsau đó để nilvào viewWillDisappear. Tất cả các bộ điều khiển xem của tôi trong ứng dụng kế thừa BaseViewController. Theo cách đó, nếu bạn có một cái gì đó trong UI.currentVC(không phải nil...) - chắc chắn nó không ở giữa một hình ảnh động trình bày và bạn có thể yêu cầu nó trình bày UIAlertController.
Aviel Gross

1
Theo bên dưới, trình điều khiển xem gốc có thể trình bày một cái gì đó với một segue, trong trường hợp đó câu lệnh if cuối cùng của bạn không thành công, vì vậy tôi phải thêm else { if let presentedViewController = controller.presentedViewController { presentedViewController.presentViewController(self, animated: animated, completion: completion) } else { controller.presentViewController(self, animated: animated, completion: completion) } }
Niklas

27

Cải thiện câu trả lời của agilityvision , bạn sẽ cần tạo một cửa sổ với bộ điều khiển xem gốc trong suốt và hiển thị chế độ xem cảnh báo từ đó.

Tuy nhiên, miễn là bạn có một hành động trong bộ điều khiển cảnh báo của mình, bạn không cần phải giữ một tham chiếu đến cửa sổ . Là bước cuối cùng của khối xử lý hành động, bạn chỉ cần ẩn cửa sổ như một phần của nhiệm vụ dọn dẹp. Bằng cách có một tham chiếu đến cửa sổ trong khối xử lý, điều này tạo ra một tham chiếu vòng tròn tạm thời sẽ bị phá vỡ sau khi bộ điều khiển cảnh báo bị loại bỏ.

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];

Hoàn hảo, chính xác là mẹo tôi cần để loại bỏ cửa sổ, cảm ơn người bạn đời
thibaut noah

25

Giải pháp sau đây không hoạt động mặc dù có vẻ khá hứa hẹn với tất cả các phiên bản. Giải pháp này đang tạo CẢNH BÁO .

Cảnh báo: Cố gắng trình bày về chế độ xem không nằm trong phân cấp cửa sổ!

https://stackoverflow.com/a 432487871/2369867 => Điều này có vẻ đầy hứa hẹn sau đó. Nhưng nó đã không ở trong Swift 3. Vì vậy, tôi đang trả lời điều này trong Swift 3 và đây không phải là ví dụ mẫu.

Đây là mã chức năng khá đầy đủ của chính nó một khi bạn dán bên trong bất kỳ chức năng.

Nhanh Swift 3 khép kín đang

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)

Đây là mã thử nghiệm và hoạt động trong Swift 3.


1
Mã này hoạt động hoàn hảo đối với tôi, trong bối cảnh UIAlertControll bị loại bỏ trong Đại biểu ứng dụng liên quan đến vấn đề di chuyển, trước khi bất kỳ trình điều khiển xem gốc nào được tải. Làm việc tuyệt vời, không có cảnh báo.
Duncan Babbage

3
Chỉ cần một lời nhắc nhở: bạn cần lưu trữ một tài liệu tham khảo mạnh mẽ cho bạn UIWindowhoặc nếu không, cửa sổ sẽ được phát hành và sẽ biến mất ngay sau khi ra khỏi phạm vi.
Còi báo

24

Đây là câu trả lời của huyền thoại như một phần mở rộng, đã được thử nghiệm và hoạt động trong 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)
    }

}

Ví dụ sử dụng:

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")
})

Điều này có thể được sử dụng ngay cả khi chia sẻ ứng dụng không thể truy cập!
Alfi

20

Điều này hoạt động trong Swift cho bộ điều khiển xem bình thường và ngay cả khi có bộ điều khiển điều hướng trên màn hình:

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)

1
Khi tôi bỏ qua cảnh báo, UIWindowlà không phản hồi. Một cái gì đó để làm với windowLevelcó lẽ. Làm thế nào tôi có thể làm cho nó đáp ứng?
thanh trượt

1
Âm thanh như cửa sổ mới không bị loại bỏ.
Igor Kulagin

Có vẻ như Cửa sổ không bị xóa khỏi đầu, vì vậy cần phải xóa cửa sổ sau khi hoàn tất.
soan saini

Đặt của bạn alertWindowđể nilkhi bạn kết thúc với nó.
C6Silver

13

Thêm vào câu trả lời của Zev (và quay trở lại Objective-C), bạn có thể gặp phải tình huống trong đó trình điều khiển chế độ xem gốc của bạn đang trình bày một số VC khác thông qua một segue hoặc một cái gì đó khác. Gọi trình bàyViewCont Bộ điều khiển trên VC gốc sẽ đảm nhiệm việc này:

[[UIApplication sharedApplication].keyWindow.rootViewController.presentedViewController presentViewController:alertController animated:YES completion:^{}];

Điều này đã giải quyết một vấn đề mà tôi gặp phải khi VC gốc đã phân biệt với một VC khác và thay vì trình bày bộ điều khiển cảnh báo, một cảnh báo như những báo cáo ở trên đã được đưa ra:

Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!

Tôi chưa kiểm tra nó, nhưng điều này cũng có thể cần thiết nếu VC gốc của bạn là bộ điều khiển điều hướng.


Hum Tôi đang gặp vấn đề này trong Swift và tôi không tìm thấy cách dịch mã objc của bạn sang swift, sự giúp đỡ sẽ được đánh giá cao!

2
@Mayerz dịch Objective-C sang Swift không nên là vấn đề lớn như vậy;) nhưng đây là:UIApplication.sharedApplication().keyWindow?.rootViewController?.presentedViewController?.presentViewController(controller, animated: true, completion: nil)
borchero

Cảm ơn Olivier, bạn đã đúng, nó dễ như một chiếc bánh và tôi đã dịch nó theo cách này, nhưng vấn đề nằm ở một nơi khác. Dẫu sao cũng xin cảm ơn!

Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<UIAlertController: 0x15cd4afe0>)
Mojo66

2
Tôi đã đi với cách tiếp cận tương tự, sử dụng rootViewController.presentedViewControllernếu nó không bằng không, nếu không sử dụng rootViewController. Đối với một giải pháp hoàn toàn chung chung, có thể cần phải đi theo chuỗi presentedViewControllers để có được tại topmostVC
Protongun

9

Câu trả lời của @ agilityvision được dịch sang Swift4 / iOS11. Tôi chưa sử dụng các chuỗi cục bộ, nhưng bạn có thể thay đổi dễ dàng:

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)
    }
}

Tôi đã nhận được một nền đen với câu trả lời được chấp nhận. window.backgroundColor = UIColor.clearĐã sửa mà. viewController.view.backgroundColor = UIColor.cleardường như không cần thiết
Ben Patch

Hãy nhớ rằng Apple cảnh báo về việc UIAlertControllerphân lớp: The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified. developer.apple.com/documentation/uikit/uialertcont điều khiển
Grubas

6

Tạo phần mở rộng như trong câu trả lời của Aviel Gross. Ở đây bạn có phần mở rộng Objective-C.

Ở đây Bạn có tệp tiêu đề * .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

Và thực hiện: * .m

//  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

Bạn đang sử dụng tiện ích mở rộng này trong tệp triển khai của bạn như thế này:

#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];

4

Đăng chéo câu trả lời của tôi vì hai chủ đề này không được gắn cờ là bản sao ...

Bây giờ UIViewControllerlà một phần của chuỗi phản hồi, bạn có thể làm một cái gì đó như thế này:

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)
}

4

Câu trả lời của Zev Eisenberg rất đơn giản và dễ hiểu, nhưng không phải lúc nào nó cũng hoạt động và nó có thể thất bại với thông điệp cảnh báo này:

Warning: Attempt to present <UIAlertController: 0x7fe6fd951e10>  
 on <ThisViewController: 0x7fe6fb409480> which is already presenting 
 <AnotherViewController: 0x7fe6fd109c00>

Điều này là do các cửa sổ rootViewControll không nằm ở đầu các khung nhìn được trình bày. Để sửa lỗi này, chúng ta cần đi lên chuỗi trình bày, như được hiển thị trong mã mở rộng UIAlertControll của tôi được viết bằng 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)
}

Cập nhật ngày 15/9/2017:

Đã thử nghiệm và xác nhận rằng logic trên vẫn hoạt động tuyệt vời trong hạt giống iOS 11 GM mới có. Tuy nhiên, phương pháp được bình chọn hàng đầu bởi agilityvision không: chế độ xem cảnh báo được trình bày trong một bản mới được đúcUIWindow được đặt bên dưới bàn phím và có khả năng ngăn người dùng chạm vào các nút của nó. Điều này là do trong iOS 11, tất cả các cửa sổ Độ cao hơn so với cửa sổ bàn phím được hạ xuống một mức dưới nó.

Một yếu tố của việc trình bày từ đó keyWindowlà hình ảnh động của bàn phím trượt xuống khi cảnh báo được trình bày và trượt lên một lần nữa khi cảnh báo bị loại bỏ. Nếu bạn muốn bàn phím ở đó trong khi trình bày, bạn có thể thử trình bày từ chính cửa sổ trên cùng, như được hiển thị trong đoạn mã dưới đây:

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 { $0.tintColor != nil || $0.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
        }
    }
}

Phần duy nhất không quá lớn của đoạn mã trên là nó kiểm tra tên lớp UIRemoteKeyboardWindowđể đảm bảo chúng ta cũng có thể bao gồm nó. Tuy nhiên, đoạn mã trên hoạt động rất tốt trong hạt giống GM 9, 10 và 11 GM, với màu sắc phù hợp và không có các tạo tác trượt bàn phím.


Chỉ cần xem qua nhiều câu trả lời trước đây ở đây và thấy câu trả lời của Kevin Sliech, người đang cố gắng giải quyết vấn đề tương tự bằng một cách tiếp cận tương tự nhưng đã dừng ngay việc đi lên chuỗi trình bày, do đó dễ bị lỗi tương tự khi cố gắng giải quyết .
CodeBrew

4

Swift 4+

Giải pháp tôi sử dụng trong nhiều năm mà không có vấn đề gì cả. Trước hết tôi mở rộng UIWindowđể tìm thấy nóViewViewControll. LƯU Ý : nếu bạn sử dụng các lớp * bộ sưu tập tùy chỉnh (như menu bên), bạn nên thêm trình xử lý cho trường hợp này trong phần mở rộng sau. Sau khi nhận được hầu hết các trình điều khiển xem hàng đầu, thật dễ dàng để trình bày UIAlertControllergiống như 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
    }
  }

}

4

Đối với iOS 13, dựa trên các câu trả lời của huyền thoại và trình duyệt :

Trong iOS 13, nếu bạn đang tạo cửa sổ của riêng mình để hiển thị cảnh báo, bạn được yêu cầu giữ tham chiếu mạnh đến cửa sổ đó nếu không cảnh báo của bạn sẽ không được hiển thị vì cửa sổ sẽ bị xử lý ngay lập tức khi tham chiếu của nó thoát khỏi phạm vi.

Hơn nữa, bạn sẽ cần đặt lại tham chiếu thành không sau khi cảnh báo bị loại bỏ để xóa cửa sổ để tiếp tục cho phép người dùng tương tác trên cửa sổ chính bên dưới nó.

Bạn có thể tạo một UIViewControllerlớp con để đóng gói logic quản lý bộ nhớ cửa sổ:

class WindowAlertPresentationController: UIViewController {

    // MARK: - Properties

    private lazy var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
    private let alert: UIAlertController

    // MARK: - Initialization

    init(alert: UIAlertController) {

        self.alert = alert
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {

        fatalError("This initializer is not supported")
    }

    // MARK: - Presentation

    func present(animated: Bool, completion: (() -> Void)?) {

        window?.rootViewController = self
        window?.windowLevel = UIWindow.Level.alert + 1
        window?.makeKeyAndVisible()
        present(alert, animated: animated, completion: completion)
    }

    // MARK: - Overrides

    override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {

        super.dismiss(animated: flag) {
            self.window = nil
            completion?()
        }
    }
}

Bạn có thể sử dụng phương thức này hoặc nếu bạn muốn có một phương thức tiện lợi UIAlertController, bạn có thể ném nó vào một tiện ích mở rộng:

extension UIAlertController {

    func presentInOwnWindow(animated: Bool, completion: (() -> Void)?) {

        let windowAlertPresentationController = WindowAlertPresentationController(alert: self)
        windowAlertPresentationController.present(animated: animated, completion: completion)
    }
}

Điều này không hoạt động nếu bạn cần loại bỏ cảnh báo theo cách thủ công - WindowAlertPftimeationContoder không bao giờ được cấp phát, dẫn đến giao diện người dùng bị đóng băng - không có gì tương tác do cửa sổ vẫn còn đó
JBlake

Nếu bạn muốn loại bỏ cảnh báo theo cách thủ công, hãy đảm bảo gọi dismisstrực tiếp trên WindowAlertPftimeationContoder alert.presentingViewController?.dismiss(animated: true, completion: nil)
JBlake

let alertControll = UIAlertControll (title: "title", message: "message", preferStyle: .alert); alertControll.presentInOwnWindow (hoạt hình: sai, hoàn thành: nil) hoạt động rất tốt cho tôi! Cảm ơn!
Brian

Điều này hoạt động trên iPhone 6 với iOS 12.4.5, nhưng không hoạt động trên iPhone 11 Pro với iOS 13.3.1. Không có lỗi, nhưng cảnh báo không bao giờ được hiển thị. Bất kỳ đề nghị sẽ được đánh giá cao.
jl303

Hoạt động tuyệt vời cho iOS 13. Không hoạt động trong Catalyst - một khi cảnh báo bị loại bỏ, ứng dụng không thể tương tác. Xem giải pháp của
@Peter Lapisu

3

Cách viết tắt để trình bày cảnh báo trong Objective-C:

[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:alertController animated:YES completion:nil];

Trong trường hợp alertControllercủa bạn là UIAlertControllerđối tượng.

LƯU Ý: Bạn cũng cần đảm bảo rằng lớp người trợ giúp của bạn mở rộng UIViewController


3

Nếu có ai quan tâm tôi đã tạo một phiên bản Swift 3 của câu trả lời @agilityvision. Mật mã:

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
    }
}

@Chathuranga: Tôi đã hoàn nguyên chỉnh sửa của bạn. Đó là cách xử lý lỗi của Wap là hoàn toàn không cần thiết.
Martin R

2
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
        }
    }
}

Với điều này, bạn có thể dễ dàng trình bày cảnh báo của mình như vậy

UIApplication.topMostViewController?.present(viewController, animated: true, completion: nil)

Một điều cần lưu ý là nếu có một UIAlertControll hiện đang được hiển thị, UIApplication.topMostViewControllersẽ trả về a UIAlertController. Trình bày trên đầu trang có một UIAlertControllerhành vi kỳ lạ và nên tránh. Do đó, bạn nên kiểm tra thủ công !(UIApplication.topMostViewController is UIAlertController)trước khi trình bày hoặc thêm else iftrường hợp để trả về nil nếuself 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
        }
    }
}

1

Bạn có thể gửi chế độ xem hiện tại hoặc bộ điều khiển dưới dạng tham số:

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

Vâng, điều đó là có thể và sẽ làm việc. Nhưng đối với tôi, nó có một chút mùi mã. Các tham số được truyền thường phải được yêu cầu cho phương thức được gọi để thực hiện chức năng chính của nó. Cộng với tất cả các cuộc gọi hiện tại sẽ cần phải được sửa đổi.
Murray Sagal

1

Kevin Sliech cung cấp một giải pháp tuyệt vời.

Bây giờ tôi sử dụng mã dưới đây trong lớp con UIViewControll chính của mình.

Một thay đổi nhỏ tôi đã thực hiện là kiểm tra xem liệu bộ điều khiển trình bày tốt nhất không phải là một UIViewContoder đơn giản. Nếu không, nó phải là một số VC trình bày một VC đơn giản. Vì vậy, chúng tôi trả lại VC đang được trình bày thay thế.

- (UIViewController *)bestPresentationController
{
    UIViewController *bestPresentationController = [UIApplication sharedApplication].keyWindow.rootViewController;

    if (![bestPresentationController isMemberOfClass:[UIViewController class]])
    {
        bestPresentationController = bestPresentationController.presentedViewController;
    }    

    return bestPresentationController;
}

Có vẻ như tất cả các công việc cho đến nay trong thử nghiệm của tôi.

Cảm ơn Kevin!


1

Ngoài các câu trả lời tuyệt vời được đưa ra ( agilityvision , adib , malhal ). Để đạt được hành vi xếp hàng như trong các UIAlertView cũ (tránh các cửa sổ cảnh báo chồng chéo), hãy sử dụng khối này để quan sát mức độ sẵn có của cửa sổ:

@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

Ví dụ hoàn chỉnh:

[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];
}];

Điều này sẽ cho phép bạn tránh các cửa sổ cảnh báo chồng chéo. Phương pháp tương tự có thể được sử dụng để phân tách và đưa vào bộ điều khiển xem hàng đợi cho bất kỳ số lượng lớp cửa sổ nào.


1

Tôi đã thử mọi thứ được đề cập, nhưng không thành công. Phương thức mà tôi đã sử dụng cho 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)
        }
    }
}

1

Một số câu trả lời này chỉ hoạt động một phần đối với tôi, kết hợp chúng theo phương pháp lớp sau trong AppDelegate là giải pháp cho tôi. Nó hoạt động trên iPad, trong chế độ xem UITabBarControll, trong UINavestionControll, vi khi trình bày các phương thức. Đã thử nghiệm trên iOS 10 và 13.

+ (UIViewController *)rootViewController {
    UIViewController *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;
    if (rootViewController.presentedViewController != nil)
        rootViewController = rootViewController.presentedViewController;
    return rootViewController;
}

Sử dụng:

[[AppDelegate rootViewController] presentViewController ...

1

Hỗ trợ cảnh iOS13 (khi sử dụng UIWindowScene)

import UIKit

private var windows: [String:UIWindow] = [:]

extension UIWindowScene {
    static var focused: UIWindowScene? {
        return UIApplication.shared.connectedScenes
            .first { $0.activationState == .foregroundActive && $0 is UIWindowScene } as? UIWindowScene
    }
}

class StyledAlertController: UIAlertController {

    var wid: String?

    func present(animated: Bool, completion: (() -> Void)?) {

        //let window = UIWindow(frame: UIScreen.main.bounds)
        guard let window = UIWindowScene.focused.map(UIWindow.init(windowScene:)) else {
            return
        }
        window.rootViewController = UIViewController()
        window.windowLevel = .alert + 1
        window.makeKeyAndVisible()
        window.rootViewController!.present(self, animated: animated, completion: completion)

        wid = UUID().uuidString
        windows[wid!] = window
    }

    open override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        if let wid = wid {
            windows[wid] = nil
        }

    }

}

0

Bạn có thể thử triển khai một danh mục trên UIViewControllervới mehtod như - (void)presentErrorMessage; Và bên trong phương thức đó bạn triển khai UIAlertControll và sau đó trình bày nó self. Hơn trong mã khách hàng của bạn, bạn sẽ có một cái gì đó như:

[myViewController presentErrorMessage];

Theo cách đó, bạn sẽ tránh được các tham số và cảnh báo không cần thiết về chế độ xem không nằm trong hệ thống phân cấp cửa sổ.


Ngoại trừ việc tôi không có myViewControllermã nơi xảy ra điều xấu. Đó là một phương thức tiện ích không biết gì về trình điều khiển khung nhìn gọi nó.
Murray Sagal

2
IMHO trình bày bất kỳ chế độ xem nào (do đó cảnh báo) cho người dùng là trách nhiệm của ViewControllers. Vì vậy, nếu một phần của mã không biết gì về viewContoder thì nó không nên xuất hiện bất kỳ lỗi nào cho người dùng mà thay vào đó chuyển chúng sang các phần "nhận biết của trình điều khiển" của mã
Vlad Soroka

2
Tôi đồng ý. Nhưng sự tiện lợi của bây giờ không được dùng nữa UIAlertViewđã khiến tôi phá vỡ quy tắc đó ở một vài điểm.
Murray Sagal

0

Có 2 cách tiếp cận mà bạn có thể sử dụng:

-Sử dụng UIAlertViewhoặc 'UIActionSheet' thay vào đó (không được đề xuất, vì nó không dùng nữa trong iOS 8 nhưng hiện tại nó hoạt động)

-Một cách nào đó hãy nhớ bộ điều khiển xem cuối cùng được trình bày. Đây là ví dụ.

@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 

Sử dụng:

[[UIViewController topViewController] presentViewController:alertController ...];

0

Tôi sử dụng mã này với một số biến thể cá nhân nhỏ trong lớp AppDelegate của mình

-(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;
}

0

Có vẻ như để làm việc:

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];
}

0

tạo lớp trợ giúp AlertWindow và hơn là sử dụng như

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);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.