วิธีการนำเสนอ UIAlertController เมื่อไม่อยู่ในตัวควบคุมมุมมอง


255

สถานการณ์จำลอง: ผู้ใช้แตะที่ปุ่มบนตัวควบคุมมุมมอง ตัวควบคุมมุมมองอยู่ด้านบนสุด (ชัด) ในสแตกการนำทาง Tap จะเรียกใช้เมธอดคลาสยูทิลิตี้ที่เรียกใช้ในคลาสอื่น มีสิ่งไม่ดีเกิดขึ้นที่นั่นและฉันต้องการแสดงการแจ้งเตือนตรงนั้นก่อนที่การควบคุมจะกลับไปที่ตัวควบคุมมุมมอง

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

สิ่งนี้เป็นไปได้ด้วยUIAlertView(แต่อาจไม่เหมาะสม)

ในกรณีนี้คุณนำเสนอกอย่างไรUIAlertControllerในนั้นmyUtilityMethod?

คำตอบ:


34

ฉันโพสต์คำถามที่คล้ายกันสองสามเดือนที่ผ่านมาและคิดว่าในที่สุดฉันก็แก้ปัญหาได้ ไปที่ลิงก์ที่ด้านล่างของโพสต์ของฉันหากคุณต้องการเห็นรหัส

วิธีแก้ไขคือใช้ UIWindow เพิ่มเติม

เมื่อคุณต้องการแสดง UIAlertController ของคุณ:

  1. ทำให้หน้าต่างของคุณเป็นกุญแจและหน้าต่างที่มองเห็นได้ (window.makeKeyAndVisible() )
  2. เพียงใช้อินสแตนซ์ UIViewController ธรรมดาเป็น rootViewController ของหน้าต่างใหม่ (window.rootViewController = UIViewController() )
  3. แสดง UIAlertController ของคุณบน rootViewController ของหน้าต่าง

สิ่งที่ควรทราบ:

  • UIWindow ของคุณต้องมีการอ้างอิงอย่างยิ่ง หากไม่มีการอ้างอิงอย่างชัดเจนมันจะไม่ปรากฏขึ้น (เพราะมีการเผยแพร่) ฉันแนะนำให้ใช้ทรัพย์สิน แต่ฉันก็ประสบความสำเร็จกับวัตถุที่เกี่ยวข้องวัตถุที่เกี่ยวข้อง
  • เพื่อให้แน่ใจว่าหน้าต่างปรากฏเหนือสิ่งอื่นใด (รวมถึงระบบ UIAlertControllers) ฉันจะตั้ง windowLevel ( window.windowLevel = UIWindowLevelAlert + 1)

ท้ายสุดฉันมีการติดตั้งที่สมบูรณ์หากคุณต้องการดู

https://github.com/dbettermann/DBAlertController


คุณไม่มีสิ่งนี้สำหรับ Objective-C ใช่ไหม
SAHM

2
ใช่มันใช้งานได้กับ Swift 2.0 / iOS 9 ฉันกำลังทำงานกับ Objective-C ในขณะนี้เพราะมีคนถามหามัน (อาจเป็นคุณ) ฉันจะโพสต์กลับเมื่อฉันทำเสร็จแล้ว
Dylan Bettermann

322

ที่ WWDC ฉันหยุดที่ห้องแล็บแห่งหนึ่งและถามคำถามเดียวกันกับ Apple Engineer: "วิธีปฏิบัติที่ดีที่สุดสำหรับการแสดงUIAlertControllerคืออะไร" และเขาบอกว่าพวกเขาได้รับคำถามนี้มากมายและเราพูดติดตลกว่าพวกเขาควรจะมีเซสชันอยู่ เขากล่าวว่าภายใน Apple กำลังสร้างUIWindowa โปร่งใสUIViewControllerและนำเสนอUIAlertControllerบนนั้น โดยพื้นฐานแล้วสิ่งที่อยู่ในคำตอบของ Dylan Betterman

แต่ฉันไม่ต้องการใช้คลาสย่อยUIAlertControllerเพราะจะทำให้ฉันต้องเปลี่ยนรหัสในแอพของฉัน ดังนั้นด้วยความช่วยเหลือของวัตถุที่เกี่ยวข้องฉันทำหมวดหมู่UIAlertControllerที่ให้showวิธีการใน Objective-C

นี่คือรหัสที่เกี่ยวข้อง:

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

UIWindowที่ถูกสร้างขึ้นจะถูกทำลายเมื่อUIAlertControllerมีการ dealloced UIWindowเพราะมันเป็นวัตถุเดียวที่รักษา แต่ถ้าคุณกำหนดให้UIAlertControllerกับคุณสมบัติหรือทำให้จำนวนการเก็บข้อมูลเพิ่มขึ้นโดยการเข้าถึงการแจ้งเตือนในหนึ่งในบล็อกการดำเนินการUIWindowจะอยู่บนหน้าจอล็อค UI ของคุณ UITextFieldดูรหัสการใช้งานตัวอย่างด้านบนเพื่อหลีกเลี่ยงการในกรณีที่จำเป็นต้องเข้าถึง

ฉันสร้าง GitHub repo ด้วยโครงการทดสอบ: FFGlobalAlertController


1
สิ่งที่ดี! เพียงแค่พื้นหลัง - ฉันใช้คลาสย่อยแทนวัตถุที่เกี่ยวข้องเพราะฉันใช้สวิฟท์ วัตถุที่เชื่อมโยงเป็นคุณสมบัติของรันไทม์ Objective-C และฉันไม่ต้องการที่จะขึ้นอยู่กับมัน Swift น่าจะอยู่ห่างจากการใช้งานจริงเป็นเวลาหลายปี :)
Dylan Bettermann

1
ฉันชอบความสง่างามของคำตอบของคุณ แต่ฉันอยากรู้ว่าคุณจะเกษียณหน้าต่างใหม่ได้อย่างไรและทำให้หน้าต่างเดิมเป็นกุญแจสำคัญอีกครั้ง
ดัสติน Pfannenstiel

1
หน้าต่างคีย์เป็นหน้าต่างที่มองเห็นได้สูงสุดดังนั้นความเข้าใจของฉันคือถ้าคุณลบ / ซ่อนหน้าต่าง "คีย์" หน้าต่างที่ปรากฏถัดไปจะกลายเป็น "กุญแจ"
agilityvision

19
การนำไปใช้viewDidDisappear:กับหมวดหมู่ดูเหมือนว่าแนวคิดไม่ดี viewDidDisappear:ในสาระสำคัญคุณกำลังแข่งขันกับการดำเนินการกรอบของ สำหรับตอนนี้อาจไม่เป็นไร แต่ถ้า Apple ตัดสินใจใช้วิธีดังกล่าวในอนาคตคุณจะไม่สามารถเรียกวิธีนี้ได้ (เช่นไม่มีsuperจุดเปรียบเทียบกับการใช้หลักของวิธีการจากการใช้หมวดหมู่) .
adib

5
ใช้งานได้ดี แต่จะรักษาprefersStatusBarHiddenและpreferredStatusBarStyleไม่มีคลาสย่อยพิเศษได้อย่างไร
Kevin Flachsmann

109

รวดเร็ว

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)

Objective-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 นี่คือทางออกง่ายๆที่ยอดเยี่ยม (ปัญหาที่ฉันเผชิญ: การแสดงการแจ้งเตือนใน DetailViewController ของต้นแบบ / รายละเอียดเทมเพลต - แสดงบน iPad, ไม่เคยใช้บน iPhone)
David

8
ดีคุณอาจต้องการเพิ่มในส่วนอื่น: if (rootViewController.presentedViewController! = ไม่มี) {rootViewController = rootViewController.presentedViewController; }
DivideByZer0

1
Swift 3: 'Alert' ถูกเปลี่ยนชื่อเป็น 'alert': ให้ alertController = UIAlertController (ชื่อเรื่อง: "title", ข้อความ: "message", PreferredStyle: .alert)
Kaptain

ใช้ผู้รับมอบสิทธิ์แทน!
Andrew Kirna

104

คุณสามารถทำสิ่งต่อไปนี้ด้วย 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)

12
โอ๊ะฉันยอมรับก่อนตรวจสอบ รหัสนั้นจะคืนค่าตัวควบคุมรูทวิวซึ่งในกรณีของฉันคือตัวควบคุมทิศทาง ไม่ก่อให้เกิดข้อผิดพลาด แต่การแจ้งเตือนไม่แสดง
Murray Sagal

22
Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!และผมสังเกตเห็นในคอนโซล:
Murray Sagal

1
@MurraySagal มีตัวควบคุมการนำทางคุณสามารถรับvisibleViewControllerคุณสมบัติได้ตลอดเวลาเพื่อดูว่าตัวควบคุมใดที่จะนำเสนอการแจ้งเตือนจาก ตรวจสอบเอกสาร
Lubo

2
ฉันทำเพราะฉันไม่ต้องการให้เครดิตกับงานของคนอื่น มันเป็นวิธีแก้ปัญหาของ @ZevEisenberg ซึ่งฉันแก้ไขเพื่อ swift 3.0 ถ้าฉันจะเพิ่มคำตอบอื่นแล้วฉันอาจจะได้รับการโหวตซึ่งเขาสมควรได้รับ
jeet.chanchawat

1
โอ้โหฉันคิดถึงละครทั้งหมดเมื่อวานนี้ แต่ฉันเพิ่งจะอัพเดทโพสต์สำหรับ Swift 3 ฉันไม่รู้ว่านโยบายของ SO นั้นเกี่ยวกับการอัปเดตคำตอบเก่าสำหรับเวอร์ชั่นภาษาใหม่ แต่ฉันไม่สนใจมัน ตราบใดที่คำตอบนั้นถูกต้อง!
Zev Eisenberg

34

พริตตี้ทั่วไปUIAlertController extensionสำหรับทุกกรณีและUINavigationController / หรือ UITabBarControllerยังทำงานได้ถ้ามี VC modal บนหน้าจอในขณะนี้

การใช้งาน:

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

1
ฉันใช้โซลูชันนี้และฉันพบว่ามันสมบูรณ์แบบหรูหราสะอาดตา ... แต่เมื่อเร็ว ๆ นี้ฉันต้องเปลี่ยนตัวควบคุมมุมมองรูทของฉันเป็นมุมมองที่ไม่ได้อยู่ในลำดับชั้นการดูดังนั้นรหัสนี้จึงไร้ประโยชน์ ใครที่คิดจะใช้ดิกส์นี้ต่อไปเรื่อย ๆ ?

1
ฉันใช้การรวมกันของการแก้ปัญหานี้กับ sometinhg อื่น: ฉันมีเดี่ยวUIชั้นซึ่งถือ (อ่อนแอ!) currentVCประเภทUIViewController.i มีBaseViewControllerซึ่งสืบทอดมาจากUIViewControllerชุดUI.currentVCไปselfบนviewDidAppearแล้วในnil viewWillDisappearตัวควบคุมมุมมองของฉันทั้งหมดในแอปสืบทอดBaseViewControllerมา วิธีที่ว่าถ้าคุณมีบางสิ่งบางอย่างในUI.currentVC(ยังไม่nil... ) - UIAlertControllerมันแน่นอนไม่ได้อยู่ในช่วงกลางของการเคลื่อนไหวที่นำเสนอและคุณสามารถขอได้ที่จะนำเสนอของคุณ
Aviel Gross

1
ตามมุมมองด้านล่างตัวควบคุมมุมมองรูทอาจนำเสนอบางสิ่งด้วยเซกเมนต์ซึ่งในกรณีนี้คำสั่งสุดท้ายของคุณหากคำสั่งล้มเหลวดังนั้นฉันจึงต้องเพิ่ม else { if let presentedViewController = controller.presentedViewController { presentedViewController.presentViewController(self, animated: animated, completion: completion) } else { controller.presentViewController(self, animated: animated, completion: completion) } }
Niklas

27

การปรับปรุงคำตอบของ 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];

สมบูรณ์แบบ, เคล็ดลับที่ฉันต้องการเพื่อยกเลิกหน้าต่าง, ขอบคุณเพื่อน
thibaut โนอาห์

25

วิธีแก้ไขปัญหาต่อไปนี้ใช้งานไม่ได้ถึงแม้ว่ามันจะดูดีสำหรับทุกเวอร์ชั่น การแก้ปัญหานี้คือการสร้างคำเตือน

คำเตือน:ความพยายามที่จะนำเสนอมุมมองที่ไม่ได้อยู่ในลำดับชั้นของหน้าต่าง!

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


1
รหัสนี้ทำงานอย่างสมบูรณ์แบบสำหรับฉันในบริบทที่ UIAlertController ถูกไล่ออกใน App Delegate เกี่ยวกับปัญหาการย้ายข้อมูลก่อนที่จะมีการโหลดตัวควบคุมมุมมองรูทใด ๆ ทำงานได้ดีไม่มีคำเตือน
Duncan Babbage

3
เพียงเตือนความจำ: คุณต้องเก็บการอ้างอิงที่แข็งแกร่งของคุณUIWindowมิฉะนั้นหน้าต่างจะถูกปล่อยออกมาและจะหายไปในไม่ช้าหลังจากออกนอกขอบเขต
ไซเรน

24

นี่คือคำตอบที่เป็นตำนานของโคเดอร์เป็นส่วนขยาย, ทดสอบและทำงานใน 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")
})

สิ่งนี้สามารถใช้งานได้แม้ว่า sharedApplication ไม่สามารถเข้าถึงได้
Alfi

20

สิ่งนี้ใช้ได้ใน 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)

1
เมื่อฉันยกเลิกการแจ้งเตือนUIWindowจะไม่ตอบสนอง สิ่งที่จะทำกับwindowLevelอาจ ฉันจะทำให้มันตอบสนองได้อย่างไร
แถบเลื่อน

1
เสียงเหมือนหน้าต่างใหม่ไม่ได้ถูกไล่ออก
Igor Kulagin

ดูเหมือนว่า Window จะไม่ถูกลบออกจากด้านบนดังนั้นจำเป็นต้องลบหน้าต่างเมื่อทำเสร็จแล้ว
soan saini

ตั้งค่าของคุณalertWindowเป็นnilเมื่อคุณทำเสร็จแล้ว
C6Silver

13

เมื่อเพิ่มคำตอบของ Zev (และเปลี่ยนกลับเป็น Objective-C) คุณสามารถพบกับสถานการณ์ที่คอนโทรลเลอร์มุมมองรูทของคุณกำลังนำเสนอ VC อื่น ๆ ผ่านทางซีเควสต์หรืออย่างอื่น การโทรที่นำเสนอ ViewController บนรูต 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!

ฉันไม่ได้ทำการทดสอบ แต่สิ่งนี้อาจจำเป็นถ้ารูต VC ของคุณเกิดขึ้นเป็นตัวควบคุมการนำทาง


ฉันกำลังประสบปัญหานี้ใน Swift และฉันไม่พบวิธีการแปลรหัส objc ของคุณเป็นรวดเร็วความช่วยเหลือจะได้รับการชื่นชมมาก!

2
@Mayerz การแปลวัตถุประสงค์ -C เป็น Swift ไม่ควรเป็นเรื่องใหญ่อะไร) แต่ที่นี่คุณ:UIApplication.sharedApplication().keyWindow?.rootViewController?.presentedViewController?.presentViewController(controller, animated: true, completion: nil)
borchero

ขอบคุณ Olivier คุณพูดถูกมันง่ายเหมือนพายและฉันแปลมันด้วยวิธีนี้ แต่ปัญหาอยู่ที่อื่น ขอขอบคุณ!

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
ผมไปกับวิธีการเดียวกันใช้หากไม่ได้ศูนย์ของมิฉะนั้นใช้rootViewController.presentedViewController rootViewControllerสำหรับวิธีการแก้ปัญหาทั่วไปอย่างสมบูรณ์คุณอาจจำเป็นต้องเดินตามสายโซ่presentedViewControllerเพื่อไปที่topmostVC
Protongun

9

คำตอบของ @ 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)
    }
}

ฉันได้พื้นหลังสีดำพร้อมคำตอบที่ยอมรับได้ window.backgroundColor = UIColor.clearคงที่ viewController.view.backgroundColor = UIColor.clearดูเหมือนจะไม่จำเป็น
Ben Patch

โปรดทราบว่า Apple เตือนเกี่ยวกับการทำUIAlertControllerคลาสย่อย: 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/uialertcontroller
Grubas

6

สร้างส่วนขยายเช่นเดียวกับคำตอบ Aviel Gross ที่นี่คุณมีส่วนขยาย 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

และการนำไปใช้: * .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

คุณใช้ส่วนขยายนี้ในไฟล์การใช้งานของคุณดังนี้:

#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

ข้ามโพสต์คำตอบของฉันเนื่องจากทั้งสองเธรดไม่ถูกตั้งค่าสถานะเป็นแบบสองหน้า ...

ตอนนี้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)
}

4

คำตอบของ Zev Eisenberg นั้นเรียบง่ายและตรงไปตรงมา แต่ก็ไม่ได้ผลเสมอไปและอาจล้มเหลวด้วยข้อความเตือนนี้:

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/9/2560:

ผ่านการทดสอบและยืนยันแล้วว่าตรรกะดังกล่าวยังคงใช้งานได้ดีในเมล็ดพันธุ์ iOS 11 จีเอ็มที่วางจำหน่าย อย่างไรก็ตามวิธีการโหวตสูงสุดโดย agilityvision ไม่ได้: มุมมองการแจ้งเตือนที่นำเสนอในมิ้นต์ใหม่UIWindowอยู่ด้านล่างของคีย์บอร์ดและอาจป้องกันไม่ให้ผู้ใช้แตะที่ปุ่ม นี่เป็นเพราะใน iOS 11 window ทั้งหมดระดับที่สูงกว่าของหน้าต่างคีย์บอร์ดจะลดลงไปถึงระดับที่ต่ำกว่า

สิ่งหนึ่งที่นำเสนอจาก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 { $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
        }
    }
}

ส่วนที่ไม่ใหญ่มากของโค้ดด้านบนคือตรวจสอบชื่อคลาสUIRemoteKeyboardWindowเพื่อให้แน่ใจว่าเราสามารถใส่มันได้ด้วย อย่างไรก็ตามรหัสดังกล่าวใช้งานได้ดีใน iOS 9, 10 และ 11 จีเอ็มเมล็ดพันธุ์, ด้วยสีที่ถูกต้องและไม่มีการเลื่อนคีย์บอร์ด


เพิ่งผ่านคำตอบก่อนหน้าจำนวนมากที่นี่และเห็นคำตอบของ Kevin Sliech ซึ่งกำลังพยายามแก้ไขปัญหาเดียวกันด้วยวิธีการที่คล้ายกัน แต่หยุดสั้น ๆ ในการเดินขึ้นห่วงโซ่การนำเสนอจึงทำให้อ่อนแอต่อข้อผิดพลาดเดียวกับที่พยายามแก้ไข .
CodeBrew

4

Swift 4+

โซลูชันที่ฉันใช้มานานหลายปีโดยไม่มีปัญหาเลย ก่อนอื่นผมขอขยายUIWindowให้เห็นว่าเป็น visibleViewController หมายเหตุ : หากคุณใช้คลาสคอลเล็กชันที่กำหนดเอง (เช่นเมนูด้านข้าง) คุณควรเพิ่มตัวจัดการสำหรับกรณีนี้ในส่วนขยายต่อไปนี้ หลังจากที่ได้รับด้านบนควบคุมดูส่วนใหญ่ก็เป็นเรื่องง่ายที่จะนำเสนอเช่นเดียวกับUIAlertControllerUIAlertView

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

สำหรับ iOS 13 การสร้างคำตอบโดยmythicalcoderและbobbyrehm :

ใน iOS 13 หากคุณสร้างหน้าต่างของคุณเองเพื่อแสดงการแจ้งเตือนคุณจะต้องมีการอ้างอิงที่แข็งแกร่งไปยังหน้าต่างนั้นมิฉะนั้นการแจ้งเตือนของคุณจะไม่ปรากฏเนื่องจากหน้าต่างจะถูกจัดสรรคืนทันทีเมื่อการอ้างอิงออกจากขอบเขต

นอกจากนี้คุณจะต้องตั้งค่าการอ้างอิงเป็นศูนย์อีกครั้งหลังจากปิดการแจ้งเตือนเพื่อลบหน้าต่างเพื่อให้ผู้ใช้โต้ตอบกับหน้าต่างหลักด้านล่างได้

คุณสามารถสร้างUIViewControllerคลาสย่อยเพื่อแค็ปซูลตรรกะการจัดการหน่วยความจำหน้าต่าง:

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

คุณสามารถใช้สิ่งนี้ตามที่เป็นอยู่หรือหากคุณต้องการวิธีการอำนวยความสะดวกให้กับคุณUIAlertControllerคุณสามารถเพิ่มเป็นส่วนขยายได้:

extension UIAlertController {

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

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

วิธีนี้ใช้ไม่ได้หากคุณต้องการยกเลิกการเตือนด้วยตนเอง - WindowAlertPresentationController จะไม่ถูกยกเลิกการจัดสรรส่งผลให้เกิด UI แบบตรึง - ไม่มีสิ่งใดที่สามารถโต้ตอบได้เนื่องจากหน้าต่างยังคงอยู่
JBlake

หากคุณต้องการยกเลิกการแจ้งเตือนด้วยตนเองตรวจสอบให้แน่ใจว่าได้โทรdismissบน WindowAlertPresentationController โดยตรง alert.presentingViewController?.dismiss(animated: true, completion: nil)
JBlake

ให้ alertController = UIAlertController (ชื่อเรื่อง: "title", ข้อความ: "message", PreferredStyle: .alert); alertController.presentInOwnWindow (ภาพเคลื่อนไหว: false, complete: nil) ใช้งานได้ดีสำหรับฉัน! ขอบคุณ!
Brian

สิ่งนี้ใช้ได้กับ iPhone 6 ที่ใช้ iOS 12.4.5 แต่ไม่สามารถใช้กับ iPhone 11 Pro ที่ใช้ iOS 13.3.1 ไม่มีข้อผิดพลาด แต่การแจ้งเตือนไม่เคยปรากฏขึ้น ข้อเสนอแนะใด ๆ ที่จะได้รับการชื่นชม
jl303

ใช้งานได้ดีสำหรับ iOS 13 ไม่ทำงานใน Catalyst - เมื่อการแจ้งเตือนถูกยกเลิกแอปจะไม่สามารถโต้ตอบได้ ดูโซลูชันของ
@Peter Lapisu

3

วิธีที่จะแสดงการแจ้งเตือนในวัตถุประสงค์แบบย่อ:

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

alertControllerของคุณอยู่ที่ไหนUIAlertControllerวัตถุ

หมายเหตุ: คุณจะต้องตรวจสอบให้แน่ใจว่าระดับผู้ช่วยของคุณขยายออกไป UIViewController


3

หากใครสนใจฉันสร้างคำตอบ @agilityvision เวอร์ชัน Swift 3 รหัส:

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: ฉันได้คืนการแก้ไขของคุณแล้ว “ การจัดการข้อผิดพลาด” นั้นไม่จำเป็นอย่างสมบูรณ์
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
        }
    }
}

ด้วยวิธีนี้คุณสามารถแสดงการแจ้งเตือนของคุณได้อย่างง่ายดาย

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

สิ่งหนึ่งที่ควรทราบก็คือว่าถ้ามี UIAlertController ปัจจุบันการแสดงจะกลับUIApplication.topMostViewController UIAlertControllerการนำเสนอเหนือสิ่งอื่นใดUIAlertControllerมีพฤติกรรมแปลก ๆ และควรหลีกเลี่ยง คุณควรตรวจสอบด้วยตนเอง!(UIApplication.topMostViewController is UIAlertController)ก่อนที่จะนำเสนอหรือเพิ่มelse ifเคสเพื่อส่งคืนศูนย์หากself 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

คุณสามารถส่งมุมมองหรือตัวควบคุมปัจจุบันเป็นพารามิเตอร์:

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

ใช่มันเป็นไปได้และจะทำงาน แต่สำหรับฉันมันมีกลิ่นของรหัสเล็กน้อย โดยทั่วไปพารามิเตอร์ที่ส่งผ่านควรจำเป็นสำหรับเมธอดที่เรียกใช้เพื่อทำหน้าที่เป็นฟังก์ชันหลัก รวมทั้งการโทรที่มีอยู่ทั้งหมดจะต้องมีการแก้ไข
Murray Sagal

1

Kevin Sliech เป็นทางออกที่ยอดเยี่ยม

ตอนนี้ฉันใช้โค้ดด้านล่างในคลาสย่อย UIViewController หลักของฉัน

หนึ่งการเปลี่ยนแปลงเล็ก ๆ ที่ฉันทำคือการตรวจสอบเพื่อดูว่าตัวควบคุมการนำเสนอที่ดีที่สุดไม่ใช่ UIViewController ธรรมดา ถ้าไม่เป็นต้องเป็น VC บางตัวที่แสดง VC ธรรมดา ดังนั้นเราจึงคืน VC ที่นำเสนอแทน

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

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

    return bestPresentationController;
}

ดูเหมือนว่าจะได้ผลทั้งหมดในการทดสอบของฉัน

ขอบคุณเควิน!


1

นอกจากนี้คำตอบที่ดีที่กำหนด ( agilityvision , Adib , 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];
}];

สิ่งนี้จะช่วยให้คุณหลีกเลี่ยงการซ้อนทับหน้าต่างการแจ้งเตือน สามารถใช้วิธีเดียวกันในการแยกและวางตัวควบคุมมุมมองคิวสำหรับจำนวนเลเยอร์หน้าต่างใด ๆ


1

ฉันลองทุกอย่างที่กล่าวถึง แต่ไม่ประสบความสำเร็จ วิธีการที่ฉันใช้กับ 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

คำตอบเหล่านี้บางส่วนใช้ได้กับฉันเพียงบางส่วนเท่านั้นการรวมไว้ในวิธีการเรียนต่อไปนี้ใน AppDelegate เป็นวิธีแก้ปัญหาสำหรับฉัน มันทำงานบน iPad ในมุมมอง UITabBarController ใน UINavigationController ซึ่งเป็นการนำเสนอ modal ทดสอบกับ iOS 10 และ 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;
}

การใช้งาน:

[[AppDelegate rootViewController] presentViewController ...

1

รองรับฉาก iOS13 (เมื่อใช้ 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

คุณสามารถพยายามที่จะใช้หมวดหมู่บนUIViewControllerกับ mehtod เหมือน - (void)presentErrorMessage;และและภายในวิธีการที่คุณใช้ UIAlertController selfแล้วนำเสนอใน กว่าในรหัสลูกค้าของคุณคุณจะมีสิ่งที่ชอบ:

[myViewController presentErrorMessage];

ด้วยวิธีนี้คุณจะหลีกเลี่ยงพารามิเตอร์ที่ไม่จำเป็นและคำเตือนเกี่ยวกับมุมมองที่ไม่ได้อยู่ในลำดับชั้นของหน้าต่าง


ยกเว้นว่าฉันไม่ได้myViewControllerอยู่ในรหัสที่สิ่งเลวร้ายเกิดขึ้น ที่อยู่ในวิธีการยูทิลิตี้ที่ไม่รู้อะไรเกี่ยวกับตัวควบคุมมุมมองที่เรียกว่า
Murray Sagal

2
IMHO นำเสนอมุมมองใด ๆ (เช่นการแจ้งเตือน) แก่ผู้ใช้เป็นความรับผิดชอบของ ViewControllers ดังนั้นหากส่วนหนึ่งของรหัสไม่ได้รู้อะไรเกี่ยวกับ viewController มันไม่ควรแสดงข้อผิดพลาดให้กับผู้ใช้ แต่ควรส่งผ่านไปยังส่วน "viewController aware" ของรหัส
Vlad Soroka

2
ฉันเห็นด้วย. แต่ความสะดวกสบายของผู้คัดค้านในขณะนี้UIAlertViewทำให้ฉันฝ่าฝืนกฎนั้นได้ในไม่กี่จุด
Murray Sagal

0

มี 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 ...];

0

ฉันใช้รหัสนี้กับรูปแบบส่วนบุคคลเล็กน้อยในคลาส 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;
}

0

ดูเหมือนว่าจะทำงาน:

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

สร้าง 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);
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.