Jak przedstawić UIAlertController, gdy nie ma kontrolera widoku?

255

Scenariusz: użytkownik naciska przycisk na kontrolerze widoku. Kontroler widoku jest najwyżej (oczywiście) na stosie nawigacji. Stuknięcie wywołuje metodę klasy narzędziowej wywoływaną w innej klasie. Zdarza się tam coś złego i chcę tam wyświetlić alert, zanim formant powróci do kontrolera widoku.

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

Było to możliwe dzięki UIAlertView(ale być może nie do końca właściwym).

W tym przypadku, jak można przedstawić UIAlertController, tam w prawo myUtilityMethod?

Murray Sagal
źródło

Odpowiedzi:

34

Zadałem podobne pytanie kilka miesięcy temu i myślę, że w końcu udało mi się rozwiązać problem. Kliknij link na dole mojego postu, jeśli chcesz zobaczyć kod.

Rozwiązaniem jest użycie dodatkowego interfejsu użytkownika.

Kiedy chcesz wyświetlić swój kontroler UIAlert:

  1. Ustaw okno jako okno kluczowe i widoczne ( window.makeKeyAndVisible())
  2. Wystarczy użyć zwykłej instancji UIViewController jako rootViewController nowego okna. ( window.rootViewController = UIViewController())
  3. Zaprezentuj swój UIAlertController w rootViewController swojego okna

Kilka rzeczy do zapamiętania:

  • Twoje UIWindow musi być mocno przywołane. Jeśli nie jest ściśle powiązany, nigdy się nie pojawi (ponieważ został wydany). Polecam użycie właściwości, ale odniosłem również sukces w powiązanym obiekcie .
  • Aby upewnić się, że okno pojawi się ponad wszystkim innym (włączając system UIAlertControllers), ustawiam windowLevel. ( window.windowLevel = UIWindowLevelAlert + 1)

Wreszcie mam ukończoną implementację, jeśli chcesz tylko na to spojrzeć.

https://github.com/dbettermann/DBAlertController

Dylan Bettermann
źródło
Nie masz tego dla Objective-C, prawda?
SAHM
2
Tak, działa nawet w Swift 2.0 / iOS 9. Pracuję teraz nad wersją Objective-C, ponieważ ktoś o to poprosił (może to byłeś ty). Wyślę z powrotem, kiedy skończę.
Dylan Bettermann
322

W WWDC zatrzymałem się w jednym z laboratoriów i zadałem inżynierowi Apple to samo pytanie: „Jaka była najlepsza praktyka wyświetlania UIAlertController?” Powiedział, że często zadawali sobie to pytanie, a my żartowaliśmy, że powinni byli o tym porozmawiać. Powiedział, że wewnętrznie Apple tworzy UIWindowprzezroczysty, UIViewControllera następnie prezentuje UIAlertControllerna nim. Zasadniczo, co zawiera odpowiedź Dylana Bettermana.

Ale nie chciałem używać podklasy, UIAlertControllerponieważ wymagałoby to zmiany kodu w mojej aplikacji. Tak więc za pomocą powiązanego obiektu stworzyłem kategorię, UIAlertControllerktóra zapewnia showmetodę w Objective-C.

Oto odpowiedni kod:

#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

Oto przykładowe użycie:

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

To, UIWindowco zostanie stworzone, zostanie zniszczone po UIAlertControllerzwolnieniu, ponieważ jest to jedyny obiekt, który zachowuje UIWindow. Ale jeśli przypiszesz UIAlertControllerwłaściwość lub sprawisz, że jej liczba zatrzymań zwiększy się, uzyskując dostęp do alertu w jednym z bloków akcji, UIWindowikona pozostanie na ekranie, blokując interfejs użytkownika. Zobacz przykładowy kod użycia powyżej, aby uniknąć w przypadku konieczności uzyskania dostępu UITextField.

Zrobiłem repozytorium GitHub z projektem testowym: FFGlobalAlertController

zwinność
źródło
1
Dobry towar! Tylko trochę tła - użyłem podklasy zamiast powiązanego obiektu, ponieważ użyłem Swift. Powiązane obiekty są cechą środowiska wykonawczego Objective-C i nie chciałem być od niego zależny. Swift ma zapewne wiele lat do uzyskania własnego środowiska uruchomieniowego, ale nadal. :)
Dylan Bettermann
1
Naprawdę podoba mi się elegancja twojej odpowiedzi, ale jestem ciekawy, jak wycofujesz nowe okno i czynisz oryginalne okno kluczem ponownie (co prawda, nie chowam się zbytnio z oknem).
Dustin Pfannenstiel
1
Okno klucza jest najwyżej widocznym oknem, więc rozumiem, że jeśli usuniesz / ukryjesz okno „klucza”, następne widoczne okno w dół staje się „kluczem”.
agilityvision
19
Wdrażanie viewDidDisappear:kategorii wygląda jak zły pomysł. Zasadniczo konkurujesz z implementacją frameworka viewDidDisappear:. Na razie może być w porządku, ale jeśli Apple zdecyduje się wdrożyć tę metodę w przyszłości, nie ma możliwości, aby ją wywołać (tj. Nie ma analogii supertych punktów do podstawowej implementacji metody z implementacji kategorii) .
adib
5
Działa świetnie, ale jak leczyć prefersStatusBarHiddeni preferredStatusBarStylebez dodatkowej podklasy?
Kevin Flachsmann,
109

Szybki

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)

Cel 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];
Darkngs
źródło
2
+1 To jest genialnie proste rozwiązanie. (Problem, z którym się spotkałem: Wyświetlanie alertu w szablonie DetailViewController szablonu Master / Detail - Pokazuje na iPadzie, nigdy na iPhonie)
David
8
Fajnie, możesz chcieć dodać w innej części: if (rootViewController.presentedViewController! = Zero) {rootViewController = rootViewController.presentedViewController; }
DivideByZer0
1
Swift 3: Nazwa „Alert” została zmieniona na „alert”: let alertController = UIAlertController (tytuł: „tytuł”, wiadomość: „wiadomość”, preferowany Styl: .alert)
Kaptain
Zamiast tego użyj delegata!
Andrew Kirna
104

W Swift 2.2 możesz wykonać następujące czynności:

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

I Swift 3.0:

let alertController: UIAlertController = ...
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
Zev Eisenberg
źródło
12
Ups, zaakceptowałem przed sprawdzeniem. Ten kod zwraca kontroler widoku głównego, który w moim przypadku jest kontrolerem nawigacyjnym. Nie powoduje błędu, ale alert się nie wyświetla.
Murray Sagal
22
I zauważyłem w konsoli: Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!.
Murray Sagal
1
@MurraySagal posiadający kontroler nawigacyjny możesz uzyskać visibleViewControllerwłaściwość w dowolnym momencie, aby zobaczyć, z którego kontrolera ma zostać wyświetlony alert. Sprawdź dokumenty
Lubo
2
Zrobiłem to, ponieważ nie chcę uznawać cudzej pracy. Było to rozwiązanie @ZevEisenberg, które zmodyfikowałem do wersji 3.0. Gdybym dodał kolejną odpowiedź, mógłbym dostać głosy, na które zasłużył.
jeet.chanchawat
1
Och, hej, wczoraj przegapiłem cały dramat, ale akurat zaktualizowałem post dla Swift 3. Nie wiem, jakie są zasady SO dotyczące aktualizowania starych odpowiedzi dla nowych wersji językowych, ale osobiście nie mam nic przeciwko, dopóki odpowiedź jest prawidłowa!
Zev Eisenberg,
34

Całkiem ogólny UIAlertController extensiondla wszystkich przypadków UINavigationControlleri / lub UITabBarController. Działa również, jeśli na ekranie jest teraz modalne VC.

Stosowanie:

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

To jest rozszerzenie:

//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
źródło
1
Korzystałem z tego rozwiązania i uznałem, że jest naprawdę idealne, eleganckie, czyste ... ALE, ostatnio musiałem zmienić kontroler widoku głównego na widok spoza hierarchii widoków, więc ten kod stał się bezużyteczny. Czy ktoś myśli o dixie, aby nadal go używać?
1
I użyć kombinacji tego roztworu z sometinhg innego: Mam singleton UIklasy, która przechowuje (słaby!) currentVCTypu UIViewController.I mieć BaseViewControllerktóry dziedziczy UIViewControlleri zestaw UI.currentVCdo selfna viewDidAppearpotem nilna viewWillDisappear. Wszystkie moje kontrolery widoku w aplikacji dziedziczą BaseViewController. W ten sposób, jeśli coś masz UI.currentVC(to nie jest nil...) - zdecydowanie nie jest to środek animacji prezentacji i możesz poprosić o prezentację UIAlertController.
Aviel Gross
1
Jak poniżej, kontroler widoku głównego może prezentować coś z segue, w którym to przypadku twoja ostatnia instrukcja if się nie powiedzie, więc musiałem dodać else { if let presentedViewController = controller.presentedViewController { presentedViewController.presentViewController(self, animated: animated, completion: completion) } else { controller.presentViewController(self, animated: animated, completion: completion) } }
Niklas
27

Poprawiając odpowiedź agilityvision , musisz utworzyć okno z przezroczystym kontrolerem widoku głównego i stamtąd wyświetlić widok alertu.

Jednak dopóki masz akcję kontrolera alertów, nie musisz utrzymywać odniesienia do okna . Jako ostatni krok bloku procedury obsługi wystarczy ukryć okno w ramach zadania czyszczenia. Dzięki odwołaniu do okna w bloku modułu obsługi powstaje tymczasowe odwołanie cykliczne, które zostanie zerwane po zwolnieniu kontrolera alertów.

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
źródło
Idealnie, dokładnie wskazówka, której potrzebowałem, aby zamknąć okno, dzięki kolego
thibaut noah
25

Poniższe rozwiązanie nie działało, mimo że wyglądało dość obiecująco we wszystkich wersjach. To rozwiązanie generuje OSTRZEŻENIE .

Ostrzeżenie: Spróbuj przedstawić, którego widok nie znajduje się w hierarchii okien!

https://stackoverflow.com/a/34487871/2369867 => To wygląda wtedy obiecująco. Ale to było nie w Swift 3. Odpowiadam na to w Swift 3 i nie jest to przykładowy szablon.

Jest to raczej w pełni funkcjonalny kod po wklejeniu do dowolnej funkcji.

Szybki Swift 3 samodzielny kod

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)

Jest to testowane i działa kod w Swift 3.

mityczny koder
źródło
1
Ten kod działał dla mnie idealnie, w kontekście, w którym UIAlertController był odpalany w Delegacie aplikacji w związku z problemem migracji, zanim załadowano jakikolwiek kontroler widoku root. Działa świetnie, bez ostrzeżeń.
Duncan Babbage,
3
Przypomnienie: musisz przechowywać silne odniesienie do swojego UIWindowokna, w przeciwnym razie okno zostanie zwolnione i zniknie wkrótce po wyjściu z zakresu.
Sirens,
24

Oto odpowiedź mitycznego kodera jako rozszerzenia, przetestowanego i działającego w 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)
    }

}

Przykładowe użycie:

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
źródło
Można tego użyć, nawet jeśli sharedApplication nie jest dostępny!
Alfi
20

Działa to w Swift dla normalnych kontrolerów widoku, a nawet jeśli na ekranie jest kontroler nawigacji:

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
źródło
1
Gdy odrzucam alert, UIWindownie odpowiada. Ma to coś wspólnego z windowLevelprawdopodobnie. Jak mogę sprawić, by był responsywny?
suwak
1
Wygląda na to, że nowe okno nie zostało odrzucone.
Igor Kulagin,
Wygląda na to, że okno nie jest usuwane z góry, więc po zakończeniu usuń okno.
soan saini
Ustaw alertWindowTO nilpo zakończeniu z nim.
C6Silver
13

Dodając do odpowiedzi Zev (i wracając do Celu-C), możesz spotkać się z sytuacją, w której kontroler widoku root prezentuje inne VC poprzez segue lub coś innego. Wywołanie prezentacjiViewController na root VC zajmie się tym:

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

To rozwiązało problem, który miałem, gdy główny VC przekonywał do innego VC, i zamiast prezentować kontroler alertów, wydano ostrzeżenie podobne do opisanych powyżej:

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

Nie testowałem tego, ale może to być również konieczne, jeśli Twój root VC okazuje się być kontrolerem nawigacyjnym.

Kevin Sliech
źródło
Hum. Napotykam na ten problem w Swift i nie mogę znaleźć sposobu na przetłumaczenie kodu objc na swift, pomoc byłaby bardzo mile widziana!
2
@ Mayerz tłumaczący Objective-C na Swift nie powinien być taki wielki;) ale oto jesteś:UIApplication.sharedApplication().keyWindow?.rootViewController?.presentedViewController?.presentViewController(controller, animated: true, completion: nil)
borchero
Dzięki Olivier, masz rację, to proste jak ciasto, a ja to przetłumaczyłem w ten sposób, ale problem leżał gdzie indziej. W każdym razie dzięki!
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 19.04.16
2
Poszedłem z tym samym podejściem, użyj rootViewController.presentedViewControllerif, jeśli nie jest zero, w przeciwnym razie użyj rootViewController. Aby uzyskać w pełni ogólne rozwiązanie, może być konieczne przejście łańcucha presentedViewControllers, aby dostać się do topmostVC
Protongun,
9

Odpowiedź @ agilityvision przetłumaczona na Swift4 / iOS11. Nie używałem zlokalizowanych ciągów, ale możesz to łatwo zmienić:

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
źródło
Otrzymałem czarne tło z zaakceptowaną odpowiedzią. window.backgroundColor = UIColor.clearnaprawiłem to. viewController.view.backgroundColor = UIColor.clearnie wydaje się konieczne.
Ben Patch
Należy pamiętać, że Apple ostrzega przed UIAlertControllerpodklasą: 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

Utwórz rozszerzenie jak w odpowiedzi Aviel Gross. Tutaj masz rozszerzenie Objective-C.

Tutaj masz plik nagłówka * .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

I wdrożenie: * .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

Używasz tego rozszerzenia w pliku implementacji w następujący sposób:

#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
źródło
4

Prześlij moją odpowiedź, ponieważ te dwa wątki nie są oznaczone jako duplikaty ...

Teraz UIViewControllerjest to część łańcucha odpowiedzi, możesz zrobić coś takiego:

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)
}
Mark Aufflick
źródło
4

Odpowiedź Zeva Eisenberga jest prosta i jednoznaczna, ale nie zawsze działa i może nie powieść się z tym komunikatem ostrzegawczym:

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

Wynika to z faktu, że rootViewController systemu Windows nie znajduje się na górze prezentowanych widoków. Aby to naprawić, musimy przejść do łańcucha prezentacji, jak pokazano w moim kodzie rozszerzenia UIAlertController napisanym w 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)
}

Aktualizacje 15.09.2017:

Przetestowano i potwierdzono, że powyższa logika nadal działa świetnie w nowo dostępnym ziarnie iOS 11 GM. Metoda najczęściej wybierana przez agilityvision nie: jednak widok alertu przedstawiony w nowej wersji UIWindowznajduje się pod klawiaturą i potencjalnie uniemożliwia użytkownikowi dotykanie przycisków. Wynika to z faktu, że w iOS 11 wszystkie poziomy okna wyższe niż okna klawiatury są obniżone do poziomu poniżej.

Jednym z artefaktów prezentacji keyWindowjest jednak animacja zsuwania się klawiatury po wyświetleniu alertu i przesuwania się ponownie po odrzuceniu alertu. Jeśli chcesz, aby klawiatura pozostała tam podczas prezentacji, możesz spróbować przedstawić ją z poziomu samego górnego okna, jak pokazano w poniższym kodzie:

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

Jedyną niezbyt dużą częścią powyższego kodu jest to, że sprawdza nazwę klasy, UIRemoteKeyboardWindowaby upewnić się, że możemy ją również dołączyć. Niemniej jednak powyższy kod działa świetnie w ziarnach iOS 9, 10 i 11 GM, z odpowiednim kolorem odcienia i bez przesuwanych artefaktów klawiatury.

CodeBrew
źródło
Właśnie przejrzałem wiele poprzednich odpowiedzi tutaj i zobaczyłem odpowiedź Kevina Sliecha, który próbuje rozwiązać ten sam problem z podobnym podejściem, ale zatrzymał się przed przejściem w górę łańcucha prezentacji, dzięki czemu jest podatny na ten sam błąd, gdy próbuje rozwiązać .
CodeBrew,
4

Swift 4+

Rozwiązanie, którego używam od lat, bez żadnych problemów. Przede wszystkim rozszerzam, UIWindowaby zobaczyć, że jest widocznyViewController. UWAGA : jeśli używasz niestandardowych klas kolekcji * (takich jak menu boczne), powinieneś dodać moduł obsługi dla tego przypadku w następującym rozszerzeniu. Po zdobyciu najwyżej kontrolera widoku można go łatwo przedstawić UIAlertControllertak jak 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 Bernikowicz
źródło
4

W przypadku iOS 13, w oparciu o odpowiedzi mitycznego kodera i bobbyrehm :

W iOS 13, jeśli tworzysz własne okno do wyświetlania alertu, musisz mieć silne odniesienie do tego okna, w przeciwnym razie alert nie zostanie wyświetlony, ponieważ okno zostanie natychmiast zwolnione, gdy odniesienie wyjdzie z zakresu.

Ponadto po odrzuceniu alertu należy ponownie ustawić wartość zerową, aby usunąć okno, aby umożliwić interakcję użytkownika w głównym oknie poniżej.

Możesz utworzyć UIViewControllerpodklasę, która zawiera logikę zarządzania pamięcią okna:

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

Możesz użyć tego tak, jak jest, lub jeśli chcesz mieć wygodną metodę UIAlertController, możesz wrzucić ją do rozszerzenia:

extension UIAlertController {

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

        let windowAlertPresentationController = WindowAlertPresentationController(alert: self)
        windowAlertPresentationController.present(animated: animated, completion: completion)
    }
}
Logan Gauthier
źródło
Nie działa to, jeśli trzeba ręcznie odrzucić alert - WindowAlertPresentationController nigdy nie jest odznaczany, co powoduje zawieszenie interfejsu użytkownika - nic nie jest interaktywne z powodu nadal
obecnego
Jeśli chcesz ręcznie odrzucić alert, koniecznie zadzwoń dismissbezpośrednio do WindowAlertPresentationController alert.presentingViewController?.dismiss(animated: true, completion: nil)
JBlake
let alertController = UIAlertController (tytuł: „tytuł”, wiadomość: „wiadomość”, preferowany styl: .alert); alertController.presentInOwnWindow (animowane: fałsz, zakończenie: zero) działa dla mnie świetnie! Dzięki!
Brian
Działa to na iPhone 6 z iOS 12.4.5, ale nie na iPhone 11 Pro z iOS 13.3.1. Nie ma błędu, ale alert nigdy się nie wyświetla. Wszelkie sugestie będą mile widziane.
jl303
Działa świetnie na iOS 13. Nie działa w Catalyst - po odrzuceniu alertu aplikacja nie może współpracować. Zobacz rozwiązanie
@Peter Lapisu
3

Skrócony sposób przedstawienia ostrzeżenia w Celu C:

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

Gdzie alertControllerjest twój UIAlertControllerprzedmiot

UWAGA: Musisz także upewnić się, że klasa pomocników się powiększyła UIViewController

ViperMav
źródło
3

Jeśli ktoś jest zainteresowany, stworzyłem wersję Swift 3 odpowiedzi @agilityvision. Kod:

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
źródło
@Chathuranga: Cofnąłem twoją edycję. Ta „obsługa błędów” jest całkowicie niepotrzebna.
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
        }
    }
}

Dzięki temu możesz łatwo przedstawić swój alert w ten sposób

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

Należy zauważyć, że jeśli aktualnie wyświetlany jest UIAlertController, UIApplication.topMostViewControllerzwróci UIAlertController. Prezentowanie na wierzchu UIAlertControllerma dziwne zachowanie i należy go unikać. W związku z tym należy albo ręcznie to sprawdzić !(UIApplication.topMostViewController is UIAlertController)przed przedstawieniem, albo dodać else ifskrzynkę, aby zwrócić zero, jeśliself 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
        }
    }
}
NSE Wyjątkowy
źródło
1

Możesz wysłać bieżący widok lub kontroler jako parametr:

+ (void)myUtilityMethod:(id)controller {
    // do stuff
    // something bad happened, display an alert.
}
Pablo A.
źródło
Tak, to możliwe i działałoby. Ale dla mnie ma trochę zapach kodu. Przekazane parametry powinny być na ogół wymagane, aby wywoływana metoda mogła wykonywać swoją podstawową funkcję. Ponadto wszystkie istniejące połączenia będą musiały zostać zmodyfikowane.
Murray Sagal,
1

Kevin Sliech zapewnił świetne rozwiązanie.

Teraz używam poniższego kodu w mojej głównej podklasie UIViewController.

Jedną małą zmianą, którą wprowadziłem, było sprawdzenie, czy najlepszy kontroler prezentacji nie jest zwykłym kontrolerem UIView. Jeśli nie, to musi być jakiś VC, który przedstawia zwykły VC. W ten sposób zwracamy VC, który jest prezentowany zamiast tego.

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

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

    return bestPresentationController;
}

Wydaje mi się, że wszystkie sprawdzają się do tej pory w moich testach.

Dziękuję Kevin!

Andrzej
źródło
1

Oprócz świetnych odpowiedzi ( agilityvision , adib , malhal ). Aby osiągnąć zachowanie w kolejce, jak w starych dobrych UIAlertViews (unikaj nakładania się okien alarmowych), użyj tego bloku, aby obserwować dostępność na poziomie okna:

@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

Kompletny przykład:

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

Pozwoli to uniknąć nakładania się okien alertów. Tej samej metody można użyć do oddzielenia i umieszczenia kontrolerów widoku kolejek dla dowolnej liczby warstw okien.

Roman B.
źródło
1

Próbowałem wszystkiego, co wspomniano, ale bezskutecznie. Metoda zastosowana w 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
źródło
1

Niektóre z tych odpowiedzi działały tylko częściowo dla mnie, rozwiązaniem było połączenie metody w klasie następnej w AppDelegate. Działa na iPadzie, w widokach UITabBarController, w UINavigationController, en podczas prezentacji modów. Testowane na iOS 10 i 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;
}

Stosowanie:

[[AppDelegate rootViewController] presentViewController ...
Eerko
źródło
1

Obsługa scen iOS13 (podczas korzystania z 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
źródło
0

Możesz spróbować zaimplementować kategorię za UIViewControllerpomocą mehtod typu - (void)presentErrorMessage;I, a wewnątrz tej metody zaimplementujesz UIAlertController, a następnie ją zaprezentujesz self. Niż w kodzie klienta będziesz mieć coś takiego:

[myViewController presentErrorMessage];

W ten sposób unikniesz niepotrzebnych parametrów i ostrzeżeń o braku wyświetlania widoku w hierarchii okien.

Vlad Soroka
źródło
Tyle że nie mam myViewControllerw kodzie, w którym dzieje się coś złego. Jest to metoda użyteczności, która nie wie nic o kontrolerze widoku, który ją wywołał.
Murray Sagal,
2
IMHO prezentujące użytkownikowi wszelkie widoki (a tym samym alerty) należy do ViewControllers. Więc jeśli jakaś część kodu nic nie wie o viewController, nie powinna przedstawiać żadnych błędów użytkownikowi, a raczej przekazać je do części kodu „viewController świadomych”
Vlad Soroka,
2
Zgadzam się. Ale wygoda przestarzałych UIAlertViewdoprowadziła mnie do złamania tej zasady w kilku miejscach.
Murray Sagal,
0

Istnieją 2 podejścia, których możesz użyć:

-Używaj UIAlertViewzamiast tego „UIActionSheet” (niezalecane, ponieważ jest przestarzałe w iOS 8, ale działa teraz)

- Jakoś pamiętaj ostatni kontroler widoku, który jest prezentowany. Oto przykład.

@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 

Stosowanie:

[[UIViewController topViewController] presentViewController:alertController ...];
Gralex
źródło
0

Używam tego kodu z niewielkimi osobistymi zmianami w mojej klasie 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;
}
Blaster dźwięku
źródło
0

Wydaje się działać:

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
źródło
0

utwórz klasę pomocnika AlertWindow, a następnie użyj jako

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);
    }
}
jan07
źródło