Jaki jest najlepszy sposób sprawdzenia, czy kontroler UIAlertController już prezentuje?

109

Mam widok tabeli, który po załadowaniu, każda komórka mogłaby prawdopodobnie zwrócić NSError, który wybrałem do wyświetlenia w UIAlertController. Problem polega na tym, że otrzymuję ten błąd w konsoli, jeśli zwracanych jest wiele błędów.

Ostrzeżenie: próba przedstawienia kontrolera UIAlertController: 0x14e64cb00 na MessagesMasterVC: 0x14e53d800, która jest już prezentowana (null)

Idealnie byłoby, gdyby chciał poradzić sobie z tym w mojej metodzie rozszerzenia UIAlertController.

class func simpleAlertWithMessage(message: String!) -> UIAlertController {

    let alertController = UIAlertController(title: nil, message: message, preferredStyle: UIAlertControllerStyle.Alert)
    let cancel = UIAlertAction(title: "Ok", style: .Cancel, handler: nil)

    alertController.addAction(cancel)
    return alertController
}

W oparciu o odpowiedź Matta zmieniłem rozszerzenie na rozszerzenie UIViewController, jest znacznie czystsze i oszczędza dużo kodu presentViewController.

    func showSimpleAlertWithMessage(message: String!) {

    let alertController = UIAlertController(title: nil, message: message, preferredStyle: UIAlertControllerStyle.Alert)
    let cancel = UIAlertAction(title: "Ok", style: .Cancel, handler: nil)

    alertController.addAction(cancel)

    if self.presentedViewController == nil {
        self.presentViewController(alertController, animated: true, completion: nil)
    }
}
ukryta nazwa użytkownika
źródło
Dziękujemy za przesłanie zaktualizowanego kodu.
djbp
Przeniosłem również resztę kodu (trzy wiersze do ustawienia UIAlertController) do instrukcji If, ponieważ nadal dawał następujący błąd (Próba załadowania widoku kontrolera widoku podczas cofania alokacji jest niedozwolona i może spowodować niezdefiniowane zachowanie)
Kitson
Chciałbym
polecić

Odpowiedzi:

119

To nie UIAlertController „już prezentuje”, jest to MessagesMasterVC. Kontroler widoku może jednocześnie prezentować tylko jeden inny kontroler widoku. Stąd komunikat o błędzie.

Innymi słowy, jeśli nakazałeś kontrolerowi widoku presentViewController:..., nie możesz tego zrobić ponownie, dopóki przedstawiony kontroler widoku nie zostanie odrzucony.

Możesz zapytać MessagesMasterVC, czy już prezentuje kontroler widoku, sprawdzając jego presentedViewController. Jeśli nie nil, nie mów mu presentViewController:...- już przedstawia kontroler widoku.

matowe
źródło
2
Jeśli kontroler A przedstawia kontroler B, a następnie B chce przedstawić UIAlertController, czy to zadziała? Mam ten sam błąd i nie mogę dowiedzieć się, czy B już przedstawia coś, o czym nie wiem, lub czy problem jest związany z tym, że B jest prezentowany przez A
Christopher Francisco
1
@ChristopherFrancisco Zadaj to jako nowe pytanie!
Mat
@ChristopherFrancisco Cześć, mam teraz ten sam problem, czy zadałeś mu nowe pytanie? lub gdzie możesz to rozwiązać? jeśli tak, jak?
Abed Naseri
Świetna odpowiedź, to subtelne rozróżnienie.
ScottyBlades
29
if ([self.navigationController.visibleViewController isKindOfClass:[UIAlertController class]]) {

      // UIAlertController is presenting.Here

}
Ben
źródło
22
Zawsze dobrze jest umieścić w odpowiedzi jakiś tekst wyjaśniający, co robisz. Przeczytaj, jak napisać dobrą odpowiedź .
Jørgen R
1
Nie jest to świetna odpowiedź z powodu braku wyjaśnienia, ale metoda bardzo mi pomogła - problem polegał na tym, że miałem więcej niż jedno zdarzenie wywołujące mój kod, aby przedstawić UIAlertControllerodpalenie w krótkich odstępach czasu. Sprawdź to, jeśli masz podobny problem.
ChidG
10

Cóż, powyższe sugerowane rozwiązania mają zasadniczy problem z mojego punktu widzenia:

Jeśli zapytasz swojego ViewController, czy atrybut „presentViewController” jest zerowy i odpowiedź jest fałszywa, nie możesz dojść do wniosku, że twój UIAlertController jest już przedstawiony. Może to być dowolny przedstawiony ViewController, np. PopOver. Więc moja sugestia, aby na pewno sprawdzić, czy Alert jest już na ekranie, jest następująca (rzut presentViewController jako UIAlertController):

if self.presentedViewController == nil {
   // do your presentation of the UIAlertController
   // ...
} else {
   // either the Alert is already presented, or any other view controller
   // is active (e.g. a PopOver)
   // ...

   let thePresentedVC : UIViewController? = self.presentedViewController as UIViewController?

   if thePresentedVC != nil {
      if let thePresentedVCAsAlertController : UIAlertController = thePresentedVC as? UIAlertController {
         // nothing to do , AlertController already active
         // ...
         print("Alert not necessary, already on the screen !")

      } else {
         // there is another ViewController presented
         // but it is not an UIAlertController, so do 
         // your UIAlertController-Presentation with 
         // this (presented) ViewController
         // ...
         thePresentedVC!.presentViewController(...)

         print("Alert comes up via another presented VC, e.g. a PopOver")
      }
  }

}

LukeSideWalker
źródło
5

Oto rozwiązanie, którego używam w Swift 3. Jest to funkcja, która pokazuje alert użytkownikowi i jeśli wywołasz ją wiele razy, zanim użytkownik odrzuci alert, doda nowy tekst alertu do już wyświetlanego alertu . Jeśli prezentowany jest inny widok, alert nie pojawi się. Nie wszyscy zgodzą się z tym zachowaniem, ale sprawdza się to w prostych sytuacjach.

extension UIViewController {
    func showAlert(_ msg: String, title: String = "") {
        if let currentAlert = self.presentedViewController as? UIAlertController {
            currentAlert.message = (currentAlert.message ?? "") + "\n\nUpdate:\(title): \(msg)"
            return
        }

        // create the alert
        let alert = UIAlertController(title: title, message: msg, preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))

        // show the alert
        self.present(alert, animated: true, completion: nil)
    }
}
biomiker
źródło
OK, tego potrzebowałem. Działa również w iOS 13.
Zoltan Vinkler
3

Możemy po prostu sprawdzić, czy jest przedstawiony kontroler widoku.

jeśli jest wyświetlany, sprawdź, czy jest to rodzaj UIAlertController.

    id alert = self.presentedViewController;

    if (alert && [alert isKindOfClass:[UIAlertController class]]) 
      {
           *// YES UIAlertController is already presented*
      }
    else
       {
        // UIAlertController is not presented OR visible.
       }
Ravi
źródło
1

możesz sprawdzić - w jednej linii - czy alert już się pojawił:

if self.presentedViewController as? UIAlertController != nil {
    print ("alert already presented")
}
Thierry G.
źródło
Możesz wyjaśnić kod w swojej odpowiedzi. Albo w jaki sposób dodaje istotne informacje, gdy istnieje już zaakceptowana lub wysoko oceniona odpowiedź Przeczytaj, jak napisać dobrą odpowiedź
Léa Gris
0

Użyłem tego do wykrywania, usuwania i ostrzegania.

Najpierw tworzymy alert z następującą funkcją.

 var yourAlert :UIAlertController!

 func useYouAlert (header: String, info:String){


    yourAlert = UIAlertController(title:header as String, message: info as String, preferredStyle: UIAlertControllerStyle.alert)



    let okAction = UIAlertAction(title: self.langText[62]as String, style: UIAlertActionStyle.default) { (result : UIAlertAction) -> Void in
        print("OK") 

    }


    yourAlert.addAction(okAction)
    self.present(yourAlert.addAction, animated: true, completion: nil)

}

I w innej części twojego kodu

    if yourAlert != nil {

      yourAlert.dismiss(animated: true, completion: nil)

    }
Nadzieja
źródło
0

W przypadku najnowszego języka Swift możesz użyć następujących opcji:

var alert = presentedViewController

if alert != nil && (alert is UIAlertController) {
    // YES UIAlertController is already presented*
} else {
    // UIAlertController is not presented OR visible.
}
Shahid Aslam
źródło
0

Odrzuć bieżący kontroler i przedstaw kontroler alertów jak

 func alert(_ message:String) {
  let alert = UIAlertController(title: "Error!", message: message, preferredStyle: .alert)
  alert.addAction(UIAlertAction(title: "Dismiss", style: .default, handler: nil))
  self.dismiss(animated: false, completion: nil)
  self.present(alert, animated: true,completion: nil)
    }
Faiz Ul Hassan
źródło
0

Swift 4.2+ Odpowiedź

if UIApplication.topViewController()!.isKind(of: UIAlertController.self) { 
            print("UIAlertController is presented")}

Dla tych, którzy nie wiedzą, jak zdobyć najwyższy poziom Viewcontroller

extension UIApplication {


public class func topViewController(_ base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
    if let nav = base as? UINavigationController {
        return topViewController(nav.visibleViewController)
    }
    if let tab = base as? UITabBarController {
        if let selected = tab.selectedViewController {
            return topViewController(selected)
        }
    }
    if let presented = base?.presentedViewController {
        return topViewController(presented)
    }
    return base
}}

Odpowiedź Swift 5+ „keyWindow” została wycofana w sugerowanej edycji iOS 13.0

if UIApplication.topViewController()!.isKind(of: UIAlertController.self) { 
            print("UIAlertController is presented")}

Dla tych, którzy nie wiedzą, jak zdobyć najwyższy poziom Viewcontroller

extension UIApplication {


public class func topViewController(_ base: UIViewController? = UIApplication.shared.windows.first?.rootViewController) -> UIViewController? {
    if let nav = base as? UINavigationController {
        return topViewController(nav.visibleViewController)
    }
    if let tab = base as? UITabBarController {
        if let selected = tab.selectedViewController {
            return topViewController(selected)
        }
    }
    if let presented = base?.presentedViewController {
        return topViewController(presented)
    }
    return base
}}
iOS Lifee
źródło
0

Okazało się, że muszę utworzyć kolejkę do układania żądań UIAlertController.

NSMutableArray *errorMessagesToShow; // in @interface
errorMessagesToShow=[[NSMutableArray alloc] init];  // in init

-(void)showError:(NSString *)theErrorMessage{
    if(theErrorMessage.length>0){
        [errorMessagesToShow addObject:theErrorMessage];
        [self showError1];
    }
}
-(void)showError1{
    NSString *theErrorMessage;
    if([errorMessagesToShow count]==0)return; // queue finished

    UIViewController* parentController =[[UIApplication sharedApplication]keyWindow].rootViewController;
    while( parentController.presentedViewController &&
      parentController != parentController.presentedViewController ){
        parentController = parentController.presentedViewController;
    }
    if([parentController isKindOfClass:[UIAlertController class]])return;  // busy

    // construct the alert using [errorMessagesToShow objectAtIndex:0]
    //  add to each UIAlertAction completionHandler [self showError1];
    //   then

    [errorMessagesToShow removeObjectAtIndex:0];
    [parentController presentViewController:alert animated:YES completion:nil]; 
}
Peter B. Kramer
źródło
-3

Po prostu zamknij bieżący kontroler i przedstaw ten, który chcesz, tj

self.dismiss(animated: false, completion: nil)

self.displayAlertController()

Idelfonso Gutierrez
źródło