Bagaimana cara menyajikan UIAlertController saat tidak dalam tampilan controller?

255

Skenario: Pengguna mengetuk tombol pada pengontrol tampilan. Pengontrol tampilan adalah yang paling atas (jelas) di tumpukan navigasi. Ketuk memanggil metode kelas utilitas yang dipanggil kelas lain. Hal buruk terjadi di sana dan saya ingin menampilkan peringatan di sana sebelum kontrol kembali ke pengontrol tampilan.

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

Ini dimungkinkan dengan UIAlertView(tapi mungkin tidak cukup tepat).

Dalam hal ini, bagaimana Anda menyajikan UIAlertController, di sana myUtilityMethod?

Murray Sagal
sumber

Jawaban:

34

Saya memposting pertanyaan serupa beberapa bulan yang lalu dan berpikir saya akhirnya menyelesaikan masalah. Ikuti tautan di bagian bawah posting saya jika Anda hanya ingin melihat kodenya.

Solusinya adalah menggunakan UIWindow tambahan.

Saat Anda ingin menampilkan UIAlertController Anda:

  1. Jadikan jendela sebagai kunci dan jendela yang terlihat ( window.makeKeyAndVisible())
  2. Cukup gunakan contoh UIViewController biasa sebagai rootViewController dari jendela baru. ( window.rootViewController = UIViewController())
  3. Presentasikan UIAlertController Anda di rootViewController jendela Anda

Beberapa hal yang perlu diperhatikan:

  • UIWindow Anda harus sangat dirujuk. Jika tidak sangat direferensikan, itu tidak akan pernah muncul (karena dirilis). Saya sarankan menggunakan properti, tetapi saya juga sukses dengan objek terkait .
  • Untuk memastikan bahwa jendela muncul di atas segalanya (termasuk sistem UIAlertControllers), saya mengatur windowLevel. ( window.windowLevel = UIWindowLevelAlert + 1)

Terakhir, saya memiliki implementasi yang lengkap jika Anda hanya ingin melihatnya.

https://github.com/dbettermann/DBAlertController

Dylan Bettermann
sumber
Anda tidak memiliki ini untuk Objective-C, bukan?
SAHM
2
Ya, ini bahkan bekerja di Swift 2.0 / iOS 9. Saya sedang mengerjakan versi Objective-C sekarang karena orang lain memintanya (mungkin itu Anda). Saya akan mengirim kembali setelah saya selesai.
Dylan Bettermann
322

Di WWDC, saya mampir di salah satu laboratorium dan bertanya pada Apple Engineer pertanyaan yang sama: "Apa praktik terbaik untuk menampilkan UIAlertController?" Dan dia mengatakan mereka telah banyak mendapatkan pertanyaan ini dan kami bercanda bahwa mereka seharusnya memiliki sesi tentang itu. Dia mengatakan bahwa secara internal Apple menciptakan UIWindowdengan transparan UIViewControllerdan kemudian menyajikannya UIAlertController. Pada dasarnya apa yang ada dalam jawaban Dylan Betterman.

Tapi saya tidak ingin menggunakan subkelas UIAlertControllerkarena itu akan mengharuskan saya mengubah kode saya di seluruh aplikasi saya. Jadi dengan bantuan objek terkait, saya membuat kategori UIAlertControlleryang menyediakan showmetode di Objective-C.

Ini kode yang relevan:

#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

Berikut ini contoh penggunaannya:

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

Yang UIWindowdibuat akan dihancurkan ketika UIAlertControllerdealloced, karena itu adalah satu-satunya objek yang mempertahankan UIWindow. Tetapi jika Anda menetapkan UIAlertControllerke properti atau menyebabkan jumlah tetapnya meningkat dengan mengakses lansiran di salah satu blok tindakan, yang UIWindowakan tetap ada di layar, mengunci UI Anda. Lihat contoh kode penggunaan di atas untuk menghindari jika perlu mengakses UITextField.

Saya membuat repo GitHub dengan proyek uji: FFGlobalAlertController

agilityvision
sumber
1
Barang bagus! Hanya beberapa latar belakang - Saya menggunakan subclass daripada objek terkait karena saya menggunakan Swift. Objek terkait adalah fitur runtime Objective-C dan saya tidak ingin bergantung padanya. Swift mungkin bertahun-tahun lagi untuk mendapatkan runtime sendiri, tapi tetap saja. :)
Dylan Bettermann
1
Saya sangat suka keanggunan jawaban Anda, namun saya ingin tahu bagaimana Anda pensiun jendela baru dan membuat jendela asli kunci lagi (memang saya tidak terlalu banyak berkutat dengan jendela).
Dustin Pfannenstiel
1
Jendela kunci adalah jendela yang terlihat paling atas, jadi pemahaman saya adalah jika Anda menghapus / menyembunyikan jendela "kunci", jendela yang terlihat berikutnya ke bawah menjadi "kunci".
agilityvision
19
Menerapkan viewDidDisappear:pada kategori tampak seperti Gagasan Buruk. Intinya, Anda bersaing dengan implementasi kerangka kerja viewDidDisappear:. Untuk saat ini mungkin baik-baik saja, tetapi jika Apple memutuskan untuk mengimplementasikan metode itu di masa depan, tidak ada cara bagi Anda untuk menyebutnya (yaitu tidak ada analoginya superyang menunjuk pada implementasi utama dari metode dari implementasi kategori) .
Adib
5
Bekerja dengan baik, tetapi bagaimana memperlakukan prefersStatusBarHiddendan preferredStatusBarStyletanpa subkelas tambahan?
Kevin Flachsmann
109

Cepat

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)

Objektif-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];
Gelap
sumber
2
+1 Ini adalah solusi yang sangat sederhana. (Masalah yang saya hadapi: Menampilkan lansiran di DetailViewController of Master / Detail template - Ditampilkan di iPad, tidak pernah di iPhone)
David
8
Bagus, Anda mungkin ingin menambahkan di bagian lain: if (rootViewController.presentedViewController! = Nil) {rootViewController = rootViewController.presentedViewController; }
DivideByZer0
1
Swift 3: 'Alert' telah diubah namanya menjadi 'alert': let alertController = UIAlertController (judul: "title", pesan: "message", preferStyle: .alert)
Kaptain
Gunakan delegasi sebagai gantinya!
Andrew Kirna
104

Anda dapat melakukan hal berikut dengan Swift 2.2:

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

Dan Swift 3.0:

let alertController: UIAlertController = ...
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
Zev Eisenberg
sumber
12
Ups, saya terima sebelum saya periksa. Kode itu mengembalikan pengontrol tampilan root, yang dalam kasus saya adalah pengontrol navigasi. Itu tidak menyebabkan kesalahan tetapi peringatan tidak ditampilkan.
Murray Sagal
22
Dan aku melihat di konsol: Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!.
Murray Sagal
1
@MurraySagal memiliki pengontrol navigasi, Anda dapat memperoleh visibleViewControllerproperti kapan saja untuk melihat dari pengontrol mana Anda dapat menyampaikan peringatan. Lihat dokumen
Lubo
2
Saya melakukannya karena saya tidak ingin mengambil kredit dari pekerjaan orang lain. Itu adalah solusi ZevEisenberg yang saya modifikasi untuk swift 3.0. Jika saya akan menambahkan jawaban lain maka saya mungkin akan mendapatkan suara yang dia layak.
jeet.chanchawat
1
Oh hei, saya ketinggalan semua drama kemarin, tapi kebetulan saya baru saja memperbarui posting untuk Swift 3. Saya tidak tahu apa kebijakan SO tentang memperbarui jawaban lama untuk versi bahasa baru, tapi saya pribadi tidak keberatan, selama jawabannya benar!
Zev Eisenberg
34

Cukup generik UIAlertController extensionuntuk semua kasus UINavigationControllerdan / atau UITabBarController. Juga berfungsi jika ada modal VC di layar saat ini.

Pemakaian:

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

Ini ekstensi:

//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)
          }
        }
    }
}
Aviel Gross
sumber
1
Saya menggunakan solusi ini, dan saya menemukan itu benar-benar sempurna, elegan, bersih ... TAPI, baru-baru ini saya harus mengubah pengontrol tampilan root saya menjadi tampilan yang tidak ada dalam hierarki tampilan, jadi kode ini menjadi tidak berguna. Adakah yang memikirkan dix untuk tetap menggunakan ini?
1
Saya menggunakan kombinasi solusi ini dengan sometinhg lain: Saya memiliki tunggal UIkelas yang memegang (lemah!) currentVCJenis UIViewController.Saya memiliki BaseViewControlleryang mewarisi dari UIViewControllerdan set UI.currentVCke selfatas viewDidAppearlalu ke nilatas viewWillDisappear. Semua pengendali tampilan saya di dalam aplikasi mewarisi BaseViewController. Dengan begitu jika Anda memiliki sesuatu UI.currentVC(bukan nil...) - pasti tidak di tengah-tengah animasi presentasi, dan Anda dapat memintanya untuk menyajikannya UIAlertController.
Aviel Gross
1
Seperti di bawah ini, pengontrol tampilan root mungkin menyajikan sesuatu dengan segue, dalam hal ini pernyataan if terakhir Anda gagal, jadi saya harus menambahkan else { if let presentedViewController = controller.presentedViewController { presentedViewController.presentViewController(self, animated: animated, completion: completion) } else { controller.presentViewController(self, animated: animated, completion: completion) } }
Niklas
27

Memperbaiki jawaban agilityvision , Anda harus membuat jendela dengan pengontrol tampilan root transparan dan menyajikan tampilan lansiran dari sana.

Namun selama Anda memiliki tindakan di pengontrol peringatan Anda , Anda tidak perlu menyimpan referensi ke jendela . Sebagai langkah terakhir dari blok pengendali tindakan, Anda hanya perlu menyembunyikan jendela sebagai bagian dari tugas pembersihan. Dengan memiliki referensi ke jendela di blok handler, ini menciptakan referensi melingkar sementara yang akan rusak setelah pengontrol lansiran dibubarkan.

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];
adib
sumber
Sempurna, persis tip yang saya butuhkan untuk menghilangkan jendela, terima kasih sobat
thibaut noah
25

Solusi berikut ini tidak berfungsi meskipun terlihat cukup menjanjikan dengan semua versi. Solusi ini menghasilkan PERINGATAN .

Peringatan: Berusaha menampilkan pandangan siapa yang tidak ada dalam hierarki jendela!

https://stackoverflow.com/a/34487871/2369867 => Ini terlihat menjanjikan saat itu. Tapi itu tidak di Swift 3. Jadi saya menjawab ini di Swift 3 dan ini bukan contoh template.

Ini adalah kode yang agak berfungsi penuh dengan sendirinya begitu Anda menempelkan di dalam fungsi apa pun.

Kode Swift 3 mandiri cepat

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)

Ini diuji dan kode kerjanya dalam Swift 3.

pembuat kode mitos
sumber
1
Kode ini berfungsi dengan baik untuk saya, dalam konteks di mana UIAlertController dipecat di App Delegate terkait masalah migrasi, sebelum pengendali tampilan root apa pun dimuat. Bekerja dengan baik, tidak ada peringatan.
Duncan Babbage
3
Hanya pengingat: Anda perlu menyimpan referensi yang kuat untuk Anda UIWindowatau jendela itu akan dirilis dan akan segera hilang setelah keluar dari ruang lingkup.
Sirene
24

Berikut jawaban mitos mister sebagai ekstensi, diuji & bekerja di 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)
    }

}

Contoh penggunaan:

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")
})
bobbyrehm
sumber
Ini dapat digunakan bahkan jika Aplikasi bersama tidak dapat diakses!
Alfi
20

Ini berfungsi di Swift untuk pengontrol tampilan normal dan bahkan jika ada pengontrol navigasi di layar:

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)
William Entriken
sumber
1
Ketika saya mengabaikan peringatan, UIWindowitu tidak responsif. Ada hubungannya dengan yang windowLevelmungkin. Bagaimana saya bisa membuatnya responsif?
slider
1
Kedengarannya seperti jendela baru tidak diberhentikan.
Igor Kulagin
Sepertinya Window tidak bisa dihapus dari atas, jadi perlu menghapus jendela setelah selesai.
soan saini
Tetapkan alertWindowuntuk nilketika Anda selesai dengan itu.
C6Silver
13

Menambahkan ke jawaban Zev (dan beralih kembali ke Objective-C), Anda bisa mengalami situasi di mana pengontrol tampilan root Anda menyajikan beberapa VC lain melalui segue atau sesuatu yang lain. Memanggil presentViewController pada root VC akan menangani hal ini:

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

Ini meluruskan masalah yang saya miliki di mana VC root telah dipisahkan ke VC lain, dan alih-alih menghadirkan pengontrol peringatan, peringatan seperti yang dilaporkan di atas dikeluarkan:

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

Saya belum mengujinya, tetapi ini mungkin juga perlu jika root VC Anda menjadi pengendali navigasi.

Kevin Sliech
sumber
Hum saya mengalami masalah ini di Swift, dan saya tidak menemukan cara menerjemahkan kode objc Anda ke swift, bantuan akan sangat dihargai!
2
@Mayerz menerjemahkan Objective-C ke Swift seharusnya tidak menjadi masalah besar;) tetapi di sini Anda:UIApplication.sharedApplication().keyWindow?.rootViewController?.presentedViewController?.presentViewController(controller, animated: true, completion: nil)
borchero
Terima kasih Olivier, Anda benar, mudah sekali, dan saya menerjemahkannya seperti ini, tetapi masalahnya ada di tempat lain. Bagaimanapun, terima kasih!
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
Saya pergi dengan pendekatan yang sama, gunakan rootViewController.presentedViewControllerjika tidak nihil, jika tidak gunakan rootViewController. Untuk solusi yang sepenuhnya generik, mungkin perlu untuk menjalankan rantai presentedViewControllers untuk mencapai topmostVC
Protongun
9

@ jawaban agilityvision diterjemahkan ke Swift4 / iOS11. Saya belum pernah menggunakan string yang dilokalkan, tetapi Anda dapat mengubahnya dengan mudah:

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)
    }
}
Dylan Colaco
sumber
Saya mendapatkan latar belakang hitam dengan jawaban yang diterima. window.backgroundColor = UIColor.clearmemperbaikinya. viewController.view.backgroundColor = UIColor.cleartampaknya tidak perlu.
Ben Patch
Perlu diingat bahwa Apple memperingatkan tentang UIAlertControllersubclassing: 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

Buat Extension seperti pada jawaban Aviel Gross. Di sini Anda memiliki ekstensi Objective-C.

Di sini Anda memiliki file header * .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

Dan implementasi: * .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

Anda menggunakan ekstensi ini di file implementasi Anda seperti ini:

#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];
Marcin Kapusta
sumber
4

Cross post jawaban saya karena kedua utas ini tidak ditandai sebagai dupes ...

Sekarang itu UIViewControlleradalah bagian dari rantai responden, Anda dapat melakukan sesuatu seperti ini:

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)
}
Tandai Aufflick
sumber
4

Jawaban Zev Eisenberg sederhana dan langsung, tetapi tidak selalu berhasil, dan mungkin gagal dengan pesan peringatan ini:

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

Ini karena windows rootViewController tidak di bagian atas tampilan yang disajikan. Untuk memperbaikinya kita perlu menjalankan rantai presentasi, seperti yang ditunjukkan dalam kode ekstensi UIAlertController saya yang ditulis dalam 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)
}

Pembaruan pada 15/15/2017:

Diuji dan dikonfirmasi bahwa logika di atas masih berfungsi dengan baik di seed iOS 11 GM yang baru tersedia. Metode pemilihan teratas oleh agilityvision, bagaimanapun, tidak: tampilan peringatan disajikan dalam yang baru dicetakUIWindow berada di bawah keyboard dan berpotensi mencegah pengguna mengetuk tombolnya. Ini karena di iOS 11 semua windowLevel lebih tinggi dari jendela keyboard diturunkan ke level di bawahnya.

Salah satu artefak presentasi dari keyWindowadalah animasi keyboard geser ke bawah ketika peringatan disajikan, dan geser ke atas saat peringatan diberhentikan. Jika Anda ingin keyboard tetap berada di sana selama presentasi, Anda dapat mencoba menyajikan dari jendela atas itu sendiri, seperti yang ditunjukkan pada kode di bawah ini:

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

Satu-satunya bagian yang tidak begitu bagus dari kode di atas adalah bahwa ia memeriksa nama kelas UIRemoteKeyboardWindowuntuk memastikan kita juga bisa memasukkannya. Namun demikian kode di atas tidak berfungsi dengan baik di iOS 9, 10 dan 11 benih GM, dengan warna warna yang tepat dan tanpa artefak sliding keyboard.

CodeBrew
sumber
Cukup telusuri banyak jawaban sebelumnya di sini dan lihat jawaban Kevin Sliech, yang mencoba menyelesaikan masalah yang sama dengan pendekatan yang serupa tetapi tidak berhenti berjalan di sepanjang rantai presentasi, sehingga membuatnya rentan terhadap kesalahan yang sama seperti mencoba menyelesaikannya .
CodeBrew
4

Swift 4+

Solusi Saya gunakan selama bertahun-tahun tanpa masalah sama sekali. Pertama-tama saya memperluas UIWindowuntuk menemukan itu terlihatViewController. CATATAN : jika Anda menggunakan kelas koleksi kustom * (seperti menu samping) Anda harus menambahkan handler untuk kasus ini dalam ekstensi berikut. Setelah mendapatkan pengontrol tampilan paling atas, mudah untuk ditampilkan UIAlertControllerseperti itu 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
    }
  }

}
Timur Bernikovich
sumber
4

Untuk iOS 13, bangun jawaban dengan mitos pembuat kode dan bobbyreh :

Di iOS 13, jika Anda membuat jendela sendiri untuk menampilkan lansiran, Anda harus memegang referensi kuat ke jendela itu atau jika tidak lansiran Anda tidak akan ditampilkan karena jendela akan segera dibatalkan alokasinya ketika referensi keluar dari ruang lingkup.

Selanjutnya, Anda perlu mengatur referensi ke nil lagi setelah lansiran diberhentikan untuk menghapus jendela untuk terus memungkinkan interaksi pengguna pada jendela utama di bawahnya.

Anda dapat membuat UIViewControllersubkelas untuk merangkum logika manajemen memori jendela:

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

Anda dapat menggunakan ini apa adanya, atau jika Anda menginginkan metode kenyamanan pada Anda UIAlertController, Anda dapat membuangnya dalam ekstensi:

extension UIAlertController {

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

        let windowAlertPresentationController = WindowAlertPresentationController(alert: self)
        windowAlertPresentationController.present(animated: animated, completion: completion)
    }
}
Logan Gauthier
sumber
Ini tidak berfungsi jika Anda harus mengabaikan lansiran secara manual - WindowAlertPresentationController tidak pernah dialokasikan, menghasilkan UI beku - tidak ada yang interaktif karena jendela masih ada
JBlake
Jika Anda ingin mengabaikan lansiran secara manual, pastikan untuk menghubungi dismissWindowAlertPresentationController secara langsung alert.presentingViewController?.dismiss(animated: true, completion: nil)
JBlake
biarkan alertController = UIAlertController (judul: "judul", pesan: "pesan", preferStyle: .alert); alertController.presentInOwnWindow (animasi: salah, selesai: nihil) sangat bagus untuk saya! Terima kasih!
Brian
Ini berfungsi pada iPhone 6 dengan iOS 12.4.5, tetapi tidak pada iPhone 11 Pro dengan iOS 13.3.1. Tidak ada kesalahan, tetapi peringatan tidak pernah ditampilkan. Setiap saran akan dihargai.
jl303
Berfungsi bagus untuk iOS 13. Tidak bekerja di Catalyst - setelah peringatan diberhentikan, aplikasi tidak dapat berinteraksi. Lihat solusi
@Peter Lapisu
3

Cara singkat untuk menyajikan peringatan di Objective-C:

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

Di mana objek alertControllerAnda UIAlertController.

CATATAN: Anda juga harus memastikan kelas pembantu Anda diperluas UIViewController

ViperMav
sumber
3

Jika ada yang tertarik, saya membuat versi @agilityvision jawaban Swift 3. Kode:

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
    }
}
Majster
sumber
@Chathuranga: Saya telah mengembalikan hasil edit Anda. "Penanganan kesalahan" itu sama sekali tidak perlu.
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
        }
    }
}

Dengan ini, Anda dapat dengan mudah menampilkan peringatan seperti itu

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

Satu hal yang perlu diperhatikan adalah jika ada UIAlertController yang sedang ditampilkan, UIApplication.topMostViewControllerakan mengembalikan a UIAlertController. Menyajikan di atas UIAlertControllermemiliki perilaku aneh dan harus dihindari. Dengan demikian, Anda harus memeriksa secara manual !(UIApplication.topMostViewController is UIAlertController)sebelum mempresentasikan, atau menambahkan else ifcase untuk mengembalikan nil jikaself 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
        }
    }
}
NSEeksepsi
sumber
1

Anda dapat mengirim tampilan atau pengontrol saat ini sebagai parameter:

+ (void)myUtilityMethod:(id)controller {
    // do stuff
    // something bad happened, display an alert.
}
Pablo A.
sumber
Ya, itu mungkin dan akan berhasil. Tetapi bagi saya, ada sedikit bau kode. Parameter yang dikirimkan secara umum harus diperlukan untuk metode yang dipanggil untuk menjalankan fungsi utamanya. Plus semua panggilan yang ada perlu diubah.
Murray Sagal
1

Kevin Sliech memberikan solusi hebat.

Saya sekarang menggunakan kode di bawah ini di subclass UIViewController utama saya.

Satu perubahan kecil yang saya buat adalah memeriksa untuk melihat apakah pengontrol presentasi terbaik bukan UIViewController biasa. Jika tidak, pasti ada beberapa VC yang menghadirkan VC biasa. Jadi kami mengembalikan VC yang disajikan sebagai gantinya.

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

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

    return bestPresentationController;
}

Tampaknya semua berhasil sejauh ini dalam pengujian saya.

Kevin terima kasih!

Andrew
sumber
1

Selain jawaban yang besar diberikan ( agilityvision , adib , malhal ). Untuk mencapai perilaku antrian seperti di UIAlertViews lama yang baik (hindari tumpang tindih jendela peringatan), gunakan blok ini untuk mengamati ketersediaan tingkat jendela:

@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

Contoh lengkap:

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

Ini akan memungkinkan Anda untuk menghindari jendela peringatan tumpang tindih. Metode yang sama dapat digunakan untuk memisahkan dan memasukkan pengontrol tampilan antrian untuk sejumlah lapisan jendela.

Roman B.
sumber
1

Saya mencoba semua yang disebutkan, tetapi tidak berhasil. Metode yang saya gunakan untuk 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)
        }
    }
}
Dragisa Dragisic
sumber
1

Beberapa jawaban ini hanya berfungsi sebagian untuk saya, menggabungkannya dalam metode kelas berikut di AppDelegate adalah solusi bagi saya. Ini berfungsi di iPad, dalam tampilan UITabBarController, di UINavigationController, dan ketika menampilkan modals. Diuji pada iOS 10 dan 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;
}

Pemakaian:

[[AppDelegate rootViewController] presentViewController ...
Eerko
sumber
1

Dukungan adegan iOS13 (saat menggunakan 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
        }

    }

}
Peter Lapisu
sumber
UIAlerController tidak boleh disubklasifikasikan menurut dokumentasi developer.apple.com/documentation/uikit/uialertcontroller
accfews
0

Anda dapat mencoba mengimplementasikan kategori UIViewControllerdengan mehtod seperti - (void)presentErrorMessage;Dan dan di dalam metode itu Anda mengimplementasikan UIAlertController dan kemudian menampilkannya self. Dari pada kode klien Anda, Anda akan memiliki sesuatu seperti:

[myViewController presentErrorMessage];

Dengan cara itu Anda akan menghindari parameter dan peringatan yang tidak perlu tentang tampilan yang tidak berada dalam hierarki jendela.

Vlad Soroka
sumber
Kecuali saya tidak punya myViewController kode di mana hal buruk terjadi. Itu dalam metode utilitas yang tidak tahu apa-apa tentang view controller yang memanggilnya.
Murray Sagal
2
IMHO yang menyajikan pandangan (dengan demikian peringatan) kepada pengguna adalah tanggung jawab ViewControllers. Jadi, jika beberapa bagian dari kode tidak tahu apa-apa tentang viewController, ia seharusnya tidak menyajikan kesalahan kepada pengguna, melainkan meneruskannya ke bagian "viewController aware" dari kode.
Vlad Soroka
2
Saya setuju. Tetapi kenyamanan yang sekarang sudah usang UIAlertViewmembuat saya melanggar peraturan itu di beberapa tempat.
Murray Sagal
0

Ada 2 pendekatan yang dapat Anda gunakan:

-Menggunakan UIAlertView atau 'UIActionSheet' sebagai gantinya (tidak disarankan, karena sudah usang di iOS 8 tetapi berfungsi sekarang)

-Beberapa ingat pengontrol tampilan terakhir yang disajikan. Berikut ini contohnya.

@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 

Pemakaian:

[[UIViewController topViewController] presentViewController:alertController ...];
Gralex
sumber
0

Saya menggunakan kode ini dengan sedikit variasi pribadi di kelas AppDelegate saya

-(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;
}
Sound Blaster
sumber
0

Tampaknya bekerja:

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];
}
wonder.mice
sumber
0

buat kelas pembantu AlertWindow dan gunakan sebagai

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);
    }
}
john07
sumber