Wie präsentiere ich UIAlertController, wenn ich mich nicht in einem View Controller befinde?

255

Szenario: Der Benutzer tippt auf eine Schaltfläche eines View Controllers. Der Ansichts-Controller ist (offensichtlich) der oberste im Navigationsstapel. Das Tippen ruft eine Dienstprogrammklassenmethode auf, die für eine andere Klasse aufgerufen wird. Dort passiert etwas Schlimmes und ich möchte genau dort eine Warnung anzeigen, bevor die Steuerung zum View Controller zurückkehrt.

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

Dies war möglich mit UIAlertView(aber vielleicht nicht ganz richtig).

Wie präsentieren Sie in diesem Fall eine UIAlertController, genau dort in myUtilityMethod?

Murray Sagal
quelle

Antworten:

34

Ich habe vor ein paar Monaten eine ähnliche Frage gestellt und denke, ich habe das Problem endlich gelöst. Folgen Sie dem Link am Ende meines Beitrags, wenn Sie nur den Code sehen möchten.

Die Lösung besteht darin, ein zusätzliches UIWindow zu verwenden.

Wenn Sie Ihren UIAlertController anzeigen möchten:

  1. Machen Sie Ihr Fenster zum Schlüssel und sichtbaren Fenster ( window.makeKeyAndVisible())
  2. Verwenden Sie einfach eine einfache UIViewController-Instanz als rootViewController des neuen Fensters. ( window.rootViewController = UIViewController())
  3. Präsentieren Sie Ihren UIAlertController auf dem rootViewController Ihres Fensters

Ein paar Dinge zu beachten:

  • Ihr UIWindow muss stark referenziert sein. Wenn nicht stark darauf verwiesen wird, wird es nie angezeigt (weil es freigegeben ist). Ich empfehle die Verwendung einer Eigenschaft, hatte aber auch Erfolg mit einem zugeordneten Objekt .
  • Um sicherzustellen, dass das Fenster über allem anderen angezeigt wird (einschließlich der System-UIAlertController), habe ich die Fensterebene festgelegt. ( window.windowLevel = UIWindowLevelAlert + 1)

Zuletzt habe ich eine abgeschlossene Implementierung, wenn Sie sich das nur ansehen möchten.

https://github.com/dbettermann/DBAlertController

Dylan Bettermann
quelle
Sie haben das nicht für Objective-C, oder?
SAHM
2
Ja, es funktioniert sogar in Swift 2.0 / iOS 9. Ich arbeite gerade an einer Objective-C-Version, weil jemand anderes danach gefragt hat (vielleicht waren Sie es). Ich werde zurück posten, wenn ich fertig bin.
Dylan Bettermann
322

Bei WWDC habe ich in einem der Labors Halt gemacht und einem Apple Engineer dieselbe Frage gestellt: "Was war die beste Vorgehensweise für die Anzeige von a UIAlertController?" Und er sagte, sie hätten diese Frage oft bekommen und wir scherzten, dass sie eine Sitzung darüber hätten haben sollen. Er sagte, dass Apple intern ein UIWindowmit einem transparenten erstellt UIViewControllerund dann das darauf präsentiert UIAlertController. Grundsätzlich, was in Dylan Bettermans Antwort steht.

Ich wollte jedoch keine Unterklasse von verwenden, UIAlertControllerda ich dafür meinen Code in meiner App ändern müsste. Mit Hilfe eines zugeordneten Objekts habe ich eine Kategorie erstellt UIAlertController, die eine showMethode in Objective-C bereitstellt .

Hier ist der relevante Code:

#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

Hier ist ein Beispiel für die Verwendung:

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

Das UIWindow, was erstellt wird, wird zerstört, wenn die UIAlertControllerZuordnung aufgehoben wird, da es das einzige Objekt ist, das das behält UIWindow. Wenn Sie das UIAlertControllerjedoch einer Eigenschaft zuweisen oder die Anzahl der Aufbewahrungen erhöhen, indem Sie auf die Warnung in einem der Aktionsblöcke zugreifen, UIWindowbleibt die auf dem Bildschirm und blockiert Ihre Benutzeroberfläche. Lesen Sie den obigen Verwendungsbeispielcode, um zu vermeiden, dass Sie darauf zugreifen müssen UITextField.

Ich habe ein GitHub-Repo mit einem Testprojekt gemacht: FFGlobalAlertController

Agilityvision
quelle
1
Gutes Zeug! Nur ein paar Hintergrundinformationen - Ich habe eine Unterklasse anstelle eines zugeordneten Objekts verwendet, weil ich Swift verwendet habe. Zugehörige Objekte sind ein Merkmal der Objective-C-Laufzeit und ich wollte nicht davon abhängig sein. Swift ist wahrscheinlich noch Jahre von seiner eigenen Laufzeit entfernt, aber immer noch. :)
Dylan Bettermann
1
Ich mag die Eleganz Ihrer Antwort sehr, aber ich bin gespannt, wie Sie das neue Fenster aus dem Verkehr ziehen und das ursprüngliche Fenster wieder zum Schlüssel machen (zugegebenermaßen mache ich nicht viel mit dem Fenster herum).
Dustin Pfannenstiel
1
Das Schlüsselfenster ist das oberste sichtbare Fenster. Wenn Sie also das "Schlüssel" -Fenster entfernen / ausblenden, wird das nächste sichtbare Fenster nach unten zum "Schlüssel".
Agilityvision
19
Die Implementierung viewDidDisappear:in einer Kategorie sieht nach einer schlechten Idee aus. Im Wesentlichen konkurrieren Sie mit der Implementierung des Frameworks von viewDidDisappear:. Im Moment mag es in Ordnung sein, aber wenn Apple beschließt, diese Methode in Zukunft zu implementieren, können Sie sie nicht aufrufen (dh es gibt keine Analogie dazu super, die auf die primäre Implementierung einer Methode aus einer Kategorieimplementierung hinweist). .
Adib
5
Funktioniert super, aber wie behandelt man prefersStatusBarHiddenund preferredStatusBarStyleohne zusätzliche Unterklasse?
Kevin Flachsmann
109

Schnell

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)

Ziel 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
quelle
2
+1 Dies ist eine genial einfache Lösung. (Problem, mit dem ich konfrontiert war: Anzeigen einer Warnung in der DetailViewController of Master / Detail-Vorlage - Wird auf dem iPad angezeigt, niemals auf dem iPhone)
David,
8
Schön, dass Sie einen weiteren Teil hinzufügen möchten: if (rootViewController.presentedViewController! = Nil) {rootViewController = rootViewController.presentedViewController; }
DivideByZer0
1
Swift 3: 'Alert' wurde in 'alert' umbenannt: let alertController = UIAlertController (Titel: "title", Nachricht: "message", PreferredStyle: .alert)
Kaptain
Verwenden Sie stattdessen einen Delegaten!
Andrew Kirna
104

Mit Swift 2.2 können Sie Folgendes tun:

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

Und Swift 3.0:

let alertController: UIAlertController = ...
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
Zev Eisenberg
quelle
12
Hoppla, ich habe zugesagt, bevor ich nachgesehen habe. Dieser Code gibt den Root-View-Controller zurück, in meinem Fall den Navigations-Controller. Es verursacht keinen Fehler, aber die Warnung wird nicht angezeigt.
Murray Sagal
22
Und ich habe in der Konsole bemerkt : Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!.
Murray Sagal
1
@MurraySagal mit einem Navigationscontroller können Sie die visibleViewControllerEigenschaft jederzeit abrufen, um zu sehen, von welchem ​​Controller die Warnung angezeigt werden soll . Schauen Sie sich die Dokumente an
Lubo
2
Ich habe es getan, weil ich keine Credits für die Arbeit eines anderen nehmen möchte. Es war @ZevEisenbergs Lösung, die ich für Swift 3.0 modifiziert habe. Wenn ich eine andere Antwort hinzugefügt hätte, hätte ich möglicherweise Abstimmungen erhalten, die er verdient.
jeet.chanchawat
1
Oh hey, ich habe gestern das ganze Drama verpasst, aber ich habe gerade den Beitrag für Swift 3 aktualisiert. Ich weiß nicht, wie SO alte Antworten für neue Sprachversionen aktualisiert, aber es macht mir persönlich nichts aus. solange die antwort richtig ist!
Zev Eisenberg
34

Ziemlich generisch UIAlertController extensionfür alle Fälle von UINavigationControllerund / oder UITabBarController. Funktioniert auch, wenn gerade eine modale VC auf dem Bildschirm angezeigt wird.

Verwendung:

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

Dies ist die Erweiterung:

//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
quelle
1
Ich habe diese Lösung verwendet und fand sie wirklich perfekt, elegant, sauber ... ABER kürzlich musste ich meinen Root-Ansichts-Controller in eine Ansicht ändern, die nicht in der Ansichtshierarchie enthalten ist, sodass dieser Code unbrauchbar wurde. Wer denkt an einen Dix, um das weiter zu benutzen?
1
Ich verwende eine Kombination dieser Lösung mit etwas anderem: Ich habe eine Singleton- UIKlasse, die einen (schwachen!) currentVCTyp enthält. UIViewControllerIch habe eine, BaseViewControllerdie von erbt UIViewControllerund UI.currentVCauf selfon und viewDidAppeardann auf nilon setzt viewWillDisappear. Alle meine View Controller in der App erben BaseViewController. Auf diese Weise, wenn Sie etwas in haben UI.currentVC(es ist nicht nil...) - es ist definitiv nicht in der Mitte einer Präsentationsanimation, und Sie können es bitten, Ihre zu präsentieren UIAlertController.
Aviel Gross
1
Wie unten dargestellt, präsentiert der Root-View-Controller möglicherweise etwas mit einem Segue. In diesem Fall schlägt Ihre letzte if-Anweisung fehl, sodass ich hinzufügen musste else { if let presentedViewController = controller.presentedViewController { presentedViewController.presentViewController(self, animated: animated, completion: completion) } else { controller.presentViewController(self, animated: animated, completion: completion) } }
Niklas
27

Um die Antwort von agilityvision zu verbessern , müssen Sie ein Fenster mit einem transparenten Root-Ansichts-Controller erstellen und von dort aus die Warnansicht anzeigen.

Aber solange Sie eine Aktion in Ihren Alarm - Controller haben, brauchen Sie nicht einen Verweis auf das Fenster zu halten . Als letzten Schritt des Aktionshandlerblocks müssen Sie nur das Fenster als Teil der Bereinigungsaufgabe ausblenden. Durch einen Verweis auf das Fenster im Handlerblock wird ein temporärer Zirkelverweis erstellt, der unterbrochen wird, sobald der Alarmcontroller geschlossen wird.

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
quelle
Perfekt, genau das Trinkgeld, das ich brauchte, um das Fenster zu schließen, danke Kumpel
Thibaut Noah
25

Die folgende Lösung funktionierte nicht , obwohl sie bei allen Versionen vielversprechend aussah. Diese Lösung generiert WARNUNG .

Warnung: Versuchen Sie zu präsentieren, auf dessen Ansicht sich nicht die Fensterhierarchie befindet!

https://stackoverflow.com/a/34487871/2369867 => Das sieht dann vielversprechend aus. Aber es war nicht in Swift 3. Ich beantworte dies in Swift 3 und dies ist kein Vorlagenbeispiel.

Dies ist ein ziemlich voll funktionsfähiger Code für sich, sobald Sie ihn in eine Funktion einfügen.

Schneller, Swift 3 in sich geschlossener Code

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)

Dies ist getesteter und funktionierender Code in Swift 3.

mythischer Kodierer
quelle
1
Dieser Code funktionierte perfekt für mich in einem Kontext, in dem ein UIAlertController im App-Delegaten wegen eines Migrationsproblems ausgelöst wurde, bevor ein Root-View-Controller geladen wurde. Hat super funktioniert, keine Warnungen.
Duncan Babbage
3
Nur zur Erinnerung: Sie müssen einen starken Verweis auf Ihr UIWindowFenster speichern, sonst wird das Fenster freigegeben und verschwindet kurz nach dem Verlassen des Gültigkeitsbereichs.
Sirenen
24

Hier ist die Antwort von mythicalcoder als Erweiterung, getestet und funktioniert in 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)
    }

}

Anwendungsbeispiel:

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
quelle
Dies kann auch verwendet werden, wenn auf sharedApplication nicht zugegriffen werden kann!
Alfi
20

Dies funktioniert in Swift für normale Ansichts-Controller und selbst wenn ein Navigations-Controller auf dem Bildschirm angezeigt wird:

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
quelle
1
Wenn ich den Alarm UIWindowablasse , reagiert der nicht. Etwas mit dem windowLevelwahrscheinlich zu tun . Wie kann ich darauf reagieren?
Schieberegler
1
Klingt so, als ob ein neues Fenster nicht geschlossen wurde.
Igor Kulagin
Es sieht so aus, als würde das Fenster nicht von oben entfernt. Sie müssen das Fenster also entfernen, sobald Sie fertig sind.
Soan Saini
Stellen Sie Ihre alertWindowauf, nilwenn Sie damit fertig sind.
C6Silver
13

Wenn Sie Zevs Antwort hinzufügen (und wieder zu Objective-C wechseln), könnten Sie in eine Situation geraten, in der Ihr Root-View-Controller eine andere VC über einen Abschnitt oder etwas anderes präsentiert. Wenn Sie den PresentViewController auf dem Root-VC aufrufen, wird Folgendes erledigt:

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

Dies hat ein Problem behoben, bei dem der Root-VC zu einem anderen VC übergegangen war, und anstatt den Alert-Controller anzuzeigen, wurde eine Warnung wie die oben angegebene ausgegeben:

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

Ich habe es nicht getestet, aber dies kann auch erforderlich sein, wenn Ihr Root-VC zufällig ein Navigationscontroller ist.

Kevin Sliech
quelle
Hum Ich stoße in Swift auf dieses Problem und finde nicht, wie ich Ihren Objektcode in Swift übersetzen kann. Hilfe wäre sehr dankbar!
2
@Mayerz Übersetzung von Objective-C nach Swift sollte keine so große Sache sein;) aber hier sind Sie:UIApplication.sharedApplication().keyWindow?.rootViewController?.presentedViewController?.presentViewController(controller, animated: true, completion: nil)
Borchero
Danke Olivier, Sie haben Recht, es ist ganz einfach, und ich habe es so übersetzt, aber das Problem lag woanders. Danke trotzdem!
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
Ich ging mit dem gleichen Ansatz, verwenden Sie die, rootViewController.presentedViewControllerwenn es nicht Null ist, sonst verwenden rootViewController. Für eine vollständig generische Lösung kann es erforderlich sein, die Kette von presentedViewControllers zu topmost
durchlaufen
9

Die Antwort von @ agilityvision wurde in Swift4 / iOS11 übersetzt. Ich habe keine lokalisierten Zeichenfolgen verwendet, aber Sie können dies leicht ändern:

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
quelle
Ich bekam einen schwarzen Hintergrund mit der akzeptierten Antwort. window.backgroundColor = UIColor.cleardas behoben. viewController.view.backgroundColor = UIColor.clearscheint nicht notwendig zu sein.
Ben Patch
UIAlertControllerThe 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.
Beachten
6

Erstellen Sie eine Erweiterung wie in der Antwort von Aviel Gross. Hier haben Sie die Objective-C-Erweiterung.

Hier haben Sie die Header-Datei * .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

Und Umsetzung: * .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

Sie verwenden diese Erweiterung in Ihrer Implementierungsdatei wie folgt:

#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
quelle
4

Kreuz poste meine Antwort da diese beiden Threads nicht als Dupes gekennzeichnet sind ...

Nun, da dies UIViewControllerTeil der Responderkette ist, können Sie Folgendes tun:

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
quelle
4

Die Antwort von Zev Eisenberg ist einfach und unkompliziert, funktioniert jedoch nicht immer und kann mit dieser Warnmeldung fehlschlagen:

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

Dies liegt daran, dass sich der Windows-RootViewController nicht oben in den dargestellten Ansichten befindet. Um dies zu korrigieren, müssen wir die Präsentationskette durchlaufen, wie in meinem in Swift 3 geschriebenen UIAlertController-Erweiterungscode gezeigt:

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

Updates am 15.09.2017:

Getestet und bestätigt, dass die obige Logik im neu verfügbaren GM-Seed für iOS 11 immer noch hervorragend funktioniert. Die von Agilityvision am besten gewählte Methode funktioniert jedoch nicht: Die Alarmansicht wird in einer neu geprägten dargestelltUIWindow befindet sich unter der Tastatur und verhindert möglicherweise, dass der Benutzer auf seine Schaltflächen tippt. Dies liegt daran, dass in iOS 11 alle Fensterebenen, die höher als die des Tastaturfensters sind, auf eine darunter liegende Ebene abgesenkt werden.

Ein Artefakt der Präsentation von keyWindowobwohl ist die Animation der Tastatur, die nach unten rutscht, wenn eine Warnung angezeigt wird, und wieder nach oben, wenn die Warnung geschlossen wird. Wenn Sie möchten, dass die Tastatur während der Präsentation dort bleibt, können Sie versuchen, sie im oberen Fenster selbst zu präsentieren, wie im folgenden Code gezeigt:

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

Der einzige nicht so große Teil des obigen Codes ist, dass er den Klassennamen überprüft UIRemoteKeyboardWindow, um sicherzustellen, dass wir ihn auch einschließen können. Trotzdem funktioniert der obige Code hervorragend in iOS 9, 10 und 11 GM-Seeds, mit der richtigen Farbtonfarbe und ohne die Artefakte der Tastatur.

CodeBrew
quelle
Ich habe gerade die vielen vorherigen Antworten hier durchgesehen und Kevin Sliechs Antwort gesehen, die versucht, dasselbe Problem mit einem ähnlichen Ansatz zu lösen, aber nicht länger die Präsentationskette hinaufläuft, wodurch sie für denselben Fehler anfällig wird, den sie zu lösen versucht .
CodeBrew
4

Swift 4+

Lösung Ich benutze seit Jahren ohne Probleme. Zunächst UIWindowmöchte ich den sichtbaren ViewController finden. HINWEIS : Wenn Sie benutzerdefinierte Collection * -Klassen (z. B. das Seitenmenü) verwenden, sollten Sie den Handler für diesen Fall in der folgenden Erweiterung hinzufügen. Obersten View - Controller Nachdem ich es einfach zu präsentieren UIAlertControllerwie 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
quelle
4

Aufbauend auf den Antworten von mythicalcoder und bobbyrehm für iOS 13 :

Wenn Sie in iOS 13 ein eigenes Fenster erstellen, in dem die Warnung angezeigt wird, müssen Sie einen starken Verweis auf dieses Fenster haben. Andernfalls wird Ihre Warnung nicht angezeigt, da das Fenster sofort freigegeben wird, wenn seine Referenz den Gültigkeitsbereich verlässt.

Darüber hinaus müssen Sie den Verweis nach dem Schließen der Warnung erneut auf Null setzen, um das Fenster zu entfernen und weiterhin Benutzerinteraktionen im Hauptfenster darunter zuzulassen.

Sie können eine UIViewControllerUnterklasse erstellen , um die Fensterspeicherverwaltungslogik zu kapseln:

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

Sie können dies so verwenden, wie es ist, oder wenn Sie eine bequeme Methode für Ihre möchten UIAlertController, können Sie sie in eine Erweiterung einfügen:

extension UIAlertController {

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

        let windowAlertPresentationController = WindowAlertPresentationController(alert: self)
        windowAlertPresentationController.present(animated: animated, completion: completion)
    }
}
Logan Gauthier
quelle
Dies funktioniert nicht, wenn Sie die Warnung manuell schließen müssen - der WindowAlertPresentationController wird nie freigegeben, was zu einer eingefrorenen Benutzeroberfläche führt - nichts ist interaktiv, da das Fenster noch vorhanden ist
JBlake
Wenn Sie die Warnung manuell schließen möchten, rufen Sie dismissden WindowAlertPresentationController direkt auf alert.presentingViewController?.dismiss(animated: true, completion: nil)
JBlake
let alertController = UIAlertController (Titel: "Titel", Nachricht: "Nachricht", PreferredStyle: .alert); alertController.presentInOwnWindow (animiert: false, Vervollständigung: nil) funktioniert hervorragend für mich! Vielen Dank!
Brian
Dies funktioniert auf dem iPhone 6 mit iOS 12.4.5, jedoch nicht auf dem iPhone 11 Pro mit iOS 13.3.1. Es liegt kein Fehler vor, aber die Warnung wird nie angezeigt. Jeder Vorschlag wäre dankbar.
jl303
Funktioniert hervorragend für iOS 13. Funktioniert nicht in Catalyst. Sobald die Warnung geschlossen wird, ist die App nicht mehr interaktiv. Siehe @Peter Lapisus Lösung
JBlake
3

Kurzform zur Darstellung der Warnung in Ziel-C:

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

Wo alertControllerist deinUIAlertController Objekt?

HINWEIS: Sie müssen auch sicherstellen, dass Ihre Hilfsklasse erweitert wird UIViewController

ViperMav
quelle
3

Wenn jemand interessiert ist, habe ich eine Swift 3-Version von @agilityvision answer erstellt. Der Code:

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
quelle
@Chathuranga: Ich habe Ihre Bearbeitung zurückgesetzt. Diese „Fehlerbehandlung“ ist völlig unnötig.
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
        }
    }
}

Mit diesem können Sie Ihre Warnung einfach so präsentieren

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

Zu beachten ist, dass, wenn derzeit ein UIAlertController angezeigt wird, a zurückgegeben UIApplication.topMostViewControllerwird UIAlertController. Präsentieren auf einem UIAlertControllerhat seltsames Verhalten und sollte vermieden werden. Daher sollten Sie dies entweder manuell überprüfen, !(UIApplication.topMostViewController is UIAlertController)bevor Sie es präsentieren, oder einen else ifFall hinzufügen , um nil if zurückzugebenself 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
        }
    }
}
NSExceptional
quelle
1

Sie können die aktuelle Ansicht oder Steuerung als Parameter senden:

+ (void)myUtilityMethod:(id)controller {
    // do stuff
    // something bad happened, display an alert.
}
Pablo A.
quelle
Ja, das ist möglich und würde funktionieren. Aber für mich riecht es ein bisschen nach Code. Übergebene Parameter sollten im Allgemeinen erforderlich sein, damit die aufgerufene Methode ihre primäre Funktion ausführen kann. Außerdem müssten alle vorhandenen Anrufe geändert werden.
Murray Sagal
1

Kevin Sliech bot eine großartige Lösung.

Ich verwende jetzt den folgenden Code in meiner Hauptunterklasse von UIViewController.

Eine kleine Änderung, die ich vorgenommen habe, war zu überprüfen, ob der beste Präsentationscontroller kein einfacher UIViewController ist. Wenn nicht, muss es eine VC sein, die eine einfache VC präsentiert. Daher geben wir die VC zurück, die stattdessen präsentiert wird.

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

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

    return bestPresentationController;
}

Scheint bei meinen Tests bisher alles geklappt zu haben.

Danke Kevin!

Andrew
quelle
1

Neben tollen Antworten ( Agilityvision , Adib , Malhal ). Verwenden Sie diesen Block, um die Verfügbarkeit von Fenstern zu beobachten, um ein Warteschlangenverhalten wie in guten alten UIAlertViews zu erreichen (Überlappung von Warnfenstern vermeiden):

@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

Vollständiges Beispiel:

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

Auf diese Weise können Sie Überlappungen von Warnfenstern vermeiden. Dieselbe Methode kann verwendet werden, um Controller für die Warteschlangenansicht für eine beliebige Anzahl von Fensterebenen zu trennen und einzufügen.

Roman B.
quelle
1

Ich habe alles versucht, aber ohne Erfolg. Die Methode, die ich für Swift 3.0 verwendet habe:

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
quelle
1

Einige dieser Antworten haben nur teilweise für mich funktioniert. Die Kombination in der folgenden Klassenmethode in AppDelegate war die Lösung für mich. Es funktioniert auf dem iPad, in UITabBarController-Ansichten, in UINavigationController und bei der Präsentation von Modalen. Getestet auf iOS 10 und 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;
}

Verwendung:

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

Unterstützung für iOS13-Szenen (bei Verwendung von 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
quelle
0

Sie können versuchen, eine Kategorie UIViewControllermit mehtod wie - (void)presentErrorMessage;And zu implementieren, und innerhalb dieser Methode implementieren Sie UIAlertController und präsentieren sie dann auf self. Dann haben Sie in Ihrem Kundencode so etwas wie:

[myViewController presentErrorMessage];

Auf diese Weise vermeiden Sie unnötige Parameter und Warnungen, dass sich die Ansicht nicht in der Fensterhierarchie befindet.

Vlad Soroka
quelle
Nur dass ich nicht myViewControllerim Code habe, wo das Schlimme passiert. Dies ist eine Dienstprogrammmethode, die nichts über den View Controller weiß, der sie aufgerufen hat.
Murray Sagal
2
IMHO liegt es in der Verantwortung von ViewControllers, dem Benutzer Ansichten (also Warnungen) zu präsentieren. Wenn also ein Teil des Codes nichts über viewController weiß, sollte er dem Benutzer keine Fehler anzeigen, sondern diese an "viewController-fähige" Teile des Codes weitergeben
Vlad Soroka
2
Genau. Aber die Bequemlichkeit des jetzt Veralteten UIAlertViewführte dazu, dass ich diese Regel an einigen Stellen brach.
Murray Sagal
0

Es gibt 2 Ansätze, die Sie verwenden können:

-Verwenden UIAlertViewSie stattdessen 'UIActionSheet' (nicht empfohlen, da es in iOS 8 veraltet ist, aber jetzt funktioniert).

- Erinnern Sie sich irgendwie an den zuletzt angezeigten View Controller. Hier ist ein Beispiel.

@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 

Verwendung:

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

Ich verwende diesen Code mit einigen kleinen persönlichen Variationen in meiner AppDelegate-Klasse

-(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
quelle
0

Scheint zu funktionieren:

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
quelle
0

Erstellen Sie die Hilfsklasse AlertWindow und verwenden Sie sie als

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
quelle