[Ios] दृश्य नियंत्रक में नहीं होने पर UIAlertController को कैसे पेश करें?


Answers

आप स्विफ्ट 2.2 के साथ निम्नलिखित कर सकते हैं:

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

और स्विफ्ट 3.0:

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

परिदृश्य: उपयोगकर्ता दृश्य नियंत्रक पर एक बटन पर टैप करता है। नेविगेशन स्टैक में व्यू कंट्रोलर सबसे ऊपर (स्पष्ट रूप से) है। टैप एक यूटिलिटी क्लास विधि को दूसरी कक्षा में बुलाता है। एक बुरी चीज होती है और मैं दृश्य नियंत्रक पर नियंत्रण रिटर्न से पहले वहां एक चेतावनी प्रदर्शित करना चाहता हूं।

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

यह UIAlertView साथ संभव था (लेकिन शायद काफी उचित नहीं)।

इस मामले में, आप UIAlertController में वहां UIAlertController कैसे प्रस्तुत करते हैं?




extension UIApplication {
    /// The top most view controller
    static var topMostViewController: UIViewController? {
        return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
    }
}

extension UIViewController {
    /// The visible view controller from a given view controller
    var visibleViewController: UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.visibleViewController
        } else if let tabBarController = self as? UITabBarController {
            return tabBarController.selectedViewController?.visibleViewController
        } else if let presentedViewController = presentedViewController {
            return presentedViewController.visibleViewController
        } else {
            return self
        }
    }
}

इसके साथ आप आसानी से अपनी अलर्ट पेश कर सकते हैं

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

ध्यान देने योग्य बात यह है कि यदि वर्तमान में प्रदर्शित होने वाला UIAlertController है, तो UIApplication.topMostViewController एक UIAlertControllerUIAlertController शीर्ष पर प्रस्तुत करना अजीब व्यवहार है और इससे बचा जाना चाहिए। इस प्रकार, आपको या तो मैन्युअल रूप से जांचना चाहिए !(UIApplication.topMostViewController is UIAlertController) प्रस्तुत करने से पहले, या else if मामला वापस लौटाता है तो अगर self is UIAlertController

extension UIViewController {
    /// The visible view controller from a given view controller
    var visibleViewController: UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.visibleViewController
        } else if let tabBarController = self as? UITabBarController {
            return tabBarController.selectedViewController?.visibleViewController
        } else if let presentedViewController = presentedViewController {
            return presentedViewController.visibleViewController
        } else if self is UIAlertController {
            return nil
        } else {
            return self
        }
    }
}



यह सामान्य दृश्य नियंत्रकों के लिए स्विफ्ट में काम करता है और यहां तक ​​कि यदि स्क्रीन पर नेविगेशन नियंत्रक है:

let alert = UIAlertController(...)

let alertWindow = UIWindow(frame: UIScreen.mainScreen().bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.presentViewController(alert, animated: true, completion: nil)



I use this code with some little personal variations in my AppDelegate class

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



दिए गए महान उत्तरों के अलावा ( agilityvision , adib , malhal )। क्यूइंग व्यवहार तक पहुंचने के लिए जैसे पुराने पुराने UIAlertViews (चेतावनी विंडोज ओवरलैप से बचें), विंडो स्तर की उपलब्धता का निरीक्षण करने के लिए इस ब्लॉक का उपयोग करें:

@interface UIWindow (WLWindowLevel)

+ (void)notifyWindowLevelIsAvailable:(UIWindowLevel)level withBlock:(void (^)())block;

@end

@implementation UIWindow (WLWindowLevel)

+ (void)notifyWindowLevelIsAvailable:(UIWindowLevel)level withBlock:(void (^)())block {
    UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
    if (keyWindow.windowLevel == level) {
        // window level is occupied, listen for windows to hide
        id observer;
        observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIWindowDidBecomeHiddenNotification object:keyWindow queue:nil usingBlock:^(NSNotification *note) {
            [[NSNotificationCenter defaultCenter] removeObserver:observer];
            [self notifyWindowLevelIsAvailable:level withBlock:block]; // recursive retry
        }];

    } else {
        block(); // window level is available
    }
}

@end

पूरा उदाहरण:

[UIWindow notifyWindowLevelIsAvailable:UIWindowLevelAlert withBlock:^{
    UIWindow *alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    alertWindow.windowLevel = UIWindowLevelAlert;
    alertWindow.rootViewController = [UIViewController new];
    [alertWindow makeKeyAndVisible];

    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:nil preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
        alertWindow.hidden = YES;
    }]];

    [alertWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
}];

This will allow you to avoid alert windows overlap. Same method can be used to separate and put in queue view controllers for any number of window layers.




मेरे पास एक ही समस्या थी, नेविगेशन नियंत्रक द्वारा प्रस्तुत UITableViewController में बैठे हुए एक अलर्ट प्रस्तुत करने का प्रयास किया। यहां जवाब और कुछ परीक्षणों के माध्यम से पढ़ने के बाद, निम्नलिखित कोड एक्सटेंशन आदि की आवश्यकता के बिना ठीक और विश्वसनीय काम किया।

self.navigationController?.present(alertController, animated: true, completion: nil)

शायद वह किसी की मदद करें ...

.. खुश कोडिंग




create helper class AlertWindow and than use as

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



There 2 approaches that you can use:

-Use UIAlertView or 'UIActionSheet' instead (not recommended, cause it deprecated in iOS 8 but it works now)

-Somehow remember the last view controller which is presented. Here is example.

@interface UIViewController (TopController)
+ (UIViewController *)topViewController;
@end

// implementation

#import "UIViewController+TopController.h"
#import <objc/runtime.h>

static __weak UIViewController *_topViewController = nil;

@implementation UIViewController (TopController)

+ (UIViewController *)topViewController {
    UIViewController *vc = _topViewController;
    while (vc.parentViewController) {
        vc = vc.parentViewController;
    }
    return vc;
}

+ (void)load {
    [super load];
    [self swizzleSelector:@selector(viewDidAppear:) withSelector:@selector(myViewDidAppear:)];
    [self swizzleSelector:@selector(viewWillDisappear:) withSelector:@selector(myViewWillDisappear:)];
}

- (void)myViewDidAppear:(BOOL)animated {
    if (_topViewController == nil) {
        _topViewController = self;
    }

    [self myViewDidAppear:animated];
}

- (void)myViewWillDisappear:(BOOL)animated {
    if (_topViewController == self) {
        _topViewController = nil;
    }

    [self myViewWillDisappear:animated];
}

+ (void)swizzleSelector:(SEL)sel1 withSelector:(SEL)sel2
{
    Class class = [self class];

    Method originalMethod = class_getInstanceMethod(class, sel1);
    Method swizzledMethod = class_getInstanceMethod(class, sel2);

    BOOL didAddMethod = class_addMethod(class,
                                        sel1,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) {
        class_replaceMethod(class,
                            sel2,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

@end 

उपयोग:

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



UINavigationController और / या UITabBarController सभी मामलों के लिए सुंदर जेनेरिक UIAlertController extension । इस समय स्क्रीन पर एक मोडल वीसी है तो भी काम करता है।

उपयोग:

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

यह विस्तार है:

//Uses Swift1.2 syntax with the new if-let
// so it won't compile on a lower version.
extension UIAlertController {

    func show() {
        present(animated: true, completion: nil)
    }

    func present(#animated: Bool, completion: (() -> Void)?) {
        if let rootVC = UIApplication.sharedApplication().keyWindow?.rootViewController {
            presentFromController(rootVC, animated: animated, completion: completion)
        }
    }

    private func presentFromController(controller: UIViewController, animated: Bool, completion: (() -> Void)?) {
        if  let navVC = controller as? UINavigationController,
            let visibleVC = navVC.visibleViewController {
                presentFromController(visibleVC, animated: animated, completion: completion)
        } else {
          if  let tabVC = controller as? UITabBarController,
              let selectedVC = tabVC.selectedViewController {
                presentFromController(selectedVC, animated: animated, completion: completion)
          } else {
              controller.presentViewController(self, animated: animated, completion: completion)
          }
        }
    }
}



जेव ईसेनबर्ग का जवाब सरल और सीधा है, लेकिन यह हमेशा काम नहीं करता है, और यह इस चेतावनी संदेश से असफल हो सकता है:

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

ऐसा इसलिए है क्योंकि Windows rootViewController प्रस्तुत दृश्यों के शीर्ष पर नहीं है। इसे ठीक करने के लिए हमें प्रस्तुति श्रृंखला को चलने की आवश्यकता है, जैसा कि स्विफ्ट 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)
}

9/15/2017 को अपडेट:

परीक्षण किया और पुष्टि की कि उपर्युक्त तर्क अभी भी नए उपलब्ध आईओएस 11 जीएम बीज में बहुत अच्छा काम करता है। Agilityvision द्वारा शीर्ष मतदान विधि, हालांकि, नहीं है: एक नए minted UIWindow में प्रस्तुत अलर्ट दृश्य कीबोर्ड से नीचे है और संभावित रूप से उपयोगकर्ता को अपने बटन टैप करने से रोकता है। ऐसा इसलिए है क्योंकि आईओएस 11 में सभी विंडो लाइब्रेरी कीबोर्ड विंडो की तुलना में अधिक है, इसके नीचे एक स्तर तक कम हो गई है।

keyWindow से प्रस्तुत करने का एक आर्टिफैक्ट हालांकि अलर्ट प्रस्तुत होने पर कुंजीपटल स्लाइडिंग की एनीमेशन है, और जब अलर्ट खारिज कर दिया जाता है तो फिर से स्लाइडिंग होती है। यदि आप प्रस्तुति के दौरान कीबोर्ड को वहां रहना चाहते हैं, तो आप निम्न विंडो में दिखाए गए अनुसार शीर्ष विंडो से उपस्थित होने का प्रयास कर सकते हैं:

func show(inViewController: UIViewController?) {
    if let vc = inViewController {
        vc.present(self, animated: true, completion: nil)
    } else {
        // get a "solid" window with the highest level
        let alertWindow = UIApplication.shared.windows.filter { $0.tintColor != nil || $0.className() == "UIRemoteKeyboardWindow" }.sorted(by: { (w1, w2) -> Bool in
            return w1.windowLevel < w2.windowLevel
        }).last
        // save the top window's tint color
        let savedTintColor = alertWindow?.tintColor
        alertWindow?.tintColor = UIApplication.shared.keyWindow?.tintColor

        // walk up the presentation tree
        var viewController = alertWindow?.rootViewController
        while viewController?.presentedViewController != nil {
            viewController = viewController?.presentedViewController
        }

        viewController?.present(self, animated: true, completion: nil)
        // restore the top window's tint color
        if let tintColor = savedTintColor {
            alertWindow?.tintColor = tintColor
        }
    }
}

उपरोक्त कोड का एकमात्र इतना बड़ा हिस्सा यह नहीं है कि यह कक्षा के नाम UIRemoteKeyboardWindow को यह सुनिश्चित करने के लिए UIRemoteKeyboardWindow है कि हम इसे भी शामिल कर सकते हैं। फिर भी उपर्युक्त कोड आईओएस 9, 10 और 11 जीएम बीज में सही टिंट रंग और कुंजीपटल स्लाइडिंग कलाकृतियों के बिना काम करता है।




Agilityvision के उत्तर में सुधार, आपको एक पारदर्शी रूट व्यू कंट्रोलर के साथ एक विंडो बनाने और वहां से अलर्ट व्यू पेश करने की आवश्यकता होगी।

हालांकि जब तक आपके अलर्ट कंट्रोलर में कोई कार्रवाई हो, तब तक आपको विंडो का संदर्भ रखने की आवश्यकता नहीं है । एक्शन हैंडलर ब्लॉक के अंतिम चरण के रूप में, आपको क्लीनअप कार्य के हिस्से के रूप में विंडो को छिपाने की आवश्यकता है। हैंडलर ब्लॉक में विंडो का संदर्भ रखते हुए, यह एक अस्थायी परिपत्र संदर्भ बनाता है जो चेतावनी नियंत्रक को खारिज कर दिया जाता है।

UIWindow* window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.rootViewController = [UIViewController new];
window.windowLevel = UIWindowLevelAlert + 1;

UIAlertController* alertCtrl = [UIAlertController alertControllerWithTitle:... message:... preferredStyle:UIAlertControllerStyleAlert];

[alertCtrl addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK",@"Generic confirm") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
    ... // do your stuff

    // very important to hide the window afterwards.
    // this also keeps a reference to the window until the action is invoked.
    window.hidden = YES;
}]];

[window makeKeyAndVisible];
[window.rootViewController presentViewController:alertCtrl animated:YES completion:nil];



I tried everything mentioned, but with no success. The method which I used for 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)
        }
    }
}



उद्देश्य-सी में चेतावनी प्रस्तुत करने के लिए शॉर्टेंड तरीका:

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

जहां alertController आपका UIAlertController ऑब्जेक्ट है।

नोट: आपको यह सुनिश्चित करने की भी आवश्यकता होगी कि आपकी सहायक कक्षा UIViewController बढ़ाती है




Swift 4+

Solution I use for years with no issues at all. First of all I extend UIWindow to find it's visibleViewController. NOTE : if you using custom collection* classes (such as side menu) you should add handler for this case in following extension. After getting top most view controller it's easy to present UIAlertController just like 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
    }
  }

}



Aviel सकल उत्तर में एक्सटेंशन बनाएँ। यहां आपके पास उद्देश्य-सी एक्सटेंशन है।

यहां आपके पास हेडर फ़ाइल * है

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

और कार्यान्वयन: * एमएम

//  UIAlertController+Showable.m

#import "UIAlertController+Showable.h"

@implementation UIAlertController (Showable)

- (void)show
{
    [self presentAnimated:YES completion:nil];
}

- (void)presentAnimated:(BOOL)animated
             completion:(void (^)(void))completion
{
    UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController;
    if (rootVC != nil) {
        [self presentFromController:rootVC animated:animated completion:completion];
    }
}

- (void)presentFromController:(UIViewController *)viewController
                     animated:(BOOL)animated
                   completion:(void (^)(void))completion
{

    if ([viewController isKindOfClass:[UINavigationController class]]) {
        UIViewController *visibleVC = ((UINavigationController *)viewController).visibleViewController;
        [self presentFromController:visibleVC animated:animated completion:completion];
    } else if ([viewController isKindOfClass:[UITabBarController class]]) {
        UIViewController *selectedVC = ((UITabBarController *)viewController).selectedViewController;
        [self presentFromController:selectedVC animated:animated completion:completion];
    } else {
        [viewController presentViewController:self animated:animated completion:completion];
    }
}

@end

आप इस कार्यान्वयन फ़ाइल में इस एक्सटेंशन का उपयोग इस तरह कर रहे हैं:

#import "UIAlertController+Showable.h"

UIAlertController* alert = [UIAlertController
    alertControllerWithTitle:@"Title here"
                     message:@"Detail message here"
              preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction* defaultAction = [UIAlertAction
    actionWithTitle:@"OK"
              style:UIAlertActionStyleDefault
            handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];

// Add more actions if needed

[alert show];