¿Cuál es la mejor manera de comprobar si ya se está presentando un UIAlertController?

109

Tengo una vista de tabla que, cuando se carga, cada celda podría devolver un NSError, que he elegido mostrar en un UIAlertController. El problema es que obtengo este error en la consola si se devuelven varios errores.

Advertencia: Intente presentar UIAlertController: 0x14e64cb00 en MessagesMasterVC: 0x14e53d800 que ya se está presentando (nulo)

Idealmente, idealmente me gustaría manejar esto en mi método de extensión 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
}

Según la respuesta de Matt, cambié la extensión a una extensión UIViewController, es mucho más limpia y ahorra mucho código 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)
    }
}
nombre de usuario oculto
fuente
Gracias por publicar su código actualizado.
djbp
También moví el resto del código (tres líneas para configurar el UIAlertController) en la declaración If, porque todavía estaba dando el siguiente error (No se permite intentar cargar la vista de un controlador de vista mientras se desasigna y puede resultar en comportamiento indefinido)
Kitson
Me gustaría referir la solución en el enlace a continuación, verifique stackoverflow.com/a/39994115/1872233
iDevAmit

Respuestas:

119

No es el UIAlertController el que "ya está presentando", es MessagesMasterVC. Un controlador de vista solo puede presentar otro controlador de vista a la vez. De ahí el mensaje de error.

En otras palabras, si le ha dicho a un controlador de vista presentViewController:...que lo haga, no puede volver a hacerlo hasta que se haya descartado el controlador de vista presentado.

Puede preguntar a MessagesMasterVC si ya está presentando un controlador de vista examinando su presentedViewController. Si no es así nil, no se lo digas, presentViewController:...ya está presentando un controlador de vista.

mate
fuente
2
Si el controlador A presenta el controlador B, y luego B quiere presentar UIAlertController, ¿funcionaría? Tengo el mismo error y no puedo calcular si B ya está presentando algo que no conozco, o si el problema se debe a que A lo presenta B
Christopher Francisco
1
@ChristopherFrancisco ¡Haz eso como una nueva pregunta!
Matt
@ChristopherFrancisco Hola, tengo el mismo problema ahora, ¿has hecho una nueva pregunta? o donde pudiste solucionarlo? si es así, ¿cómo?
Abed Naseri
Gran respuesta, esa es una distinción sutil.
ScottyBlades
29
if ([self.navigationController.visibleViewController isKindOfClass:[UIAlertController class]]) {

      // UIAlertController is presenting.Here

}
Ben
fuente
22
Siempre es una buena idea incluir algo de texto en su respuesta para explicar lo que está haciendo. Lea cómo escribir una buena respuesta .
Jørgen R
1
No es una gran respuesta debido a la falta de explicación, pero el método me ayudó mucho; el problema era que tenía más de un evento llamando a mi código para presentar un UIAlertControllerdisparo en una sucesión corta. Compruebe esto si tiene un problema similar.
ChidG
10

Bueno, las soluciones sugeridas arriba tienen un problema esencial desde mi punto de vista:

Si le pregunta a su ViewController, si el atributo 'PresentViewController' es nulo y la respuesta es falsa, no puede llegar a la conclusión de que su UIAlertController ya está presentado. Podría ser cualquier ViewController presentado, por ejemplo, un popOver. Entonces, mi sugerencia para verificar seguramente si la alerta ya está en la pantalla es la siguiente (transmita el PresentViewController como un 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
fuente
5

Aquí hay una solución que uso en Swift 3. Es una función que muestra una alerta al usuario, y si la llama varias veces antes de que el usuario haya descartado la alerta, agregará el nuevo texto de alerta a la alerta que ya se está presentando. . Si se presenta alguna otra vista, la alerta no aparecerá. No todos estarán de acuerdo con ese comportamiento, pero funciona bien para situaciones simples.

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
fuente
OK, esto es lo que necesitaba. También funciona en iOS 13.
Zoltan Vinkler
3

Simplemente podemos comprobar si se presenta algún controlador de vista.

si se presenta, compruebe si es una especie de UIAlertController.

    id alert = self.presentedViewController;

    if (alert && [alert isKindOfClass:[UIAlertController class]]) 
      {
           *// YES UIAlertController is already presented*
      }
    else
       {
        // UIAlertController is not presented OR visible.
       }
Ravi
fuente
1

puede probar, en una sola línea, si ya se ha presentado una alerta:

if self.presentedViewController as? UIAlertController != nil {
    print ("alert already presented")
}
Thierry G.
fuente
Podría explicar el código en su respuesta. O cómo agrega información relevante cuando ya hay una respuesta aceptada o altamente calificada Lea cómo escribir una buena respuesta
Léa Gris
0

Lo usé para detectar, eliminar y alertar.

Primero creamos una alerta con la siguiente función.

 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)

}

Y en alguna otra parte de tu código

    if yourAlert != nil {

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

    }
Esperanza
fuente
0

Para el idioma Swift más reciente, puede usar lo siguiente:

var alert = presentedViewController

if alert != nil && (alert is UIAlertController) {
    // YES UIAlertController is already presented*
} else {
    // UIAlertController is not presented OR visible.
}
Shahid Aslam
fuente
0

Descarte el controlador actual y presente el controlador de alerta como

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

Respuesta rápida 4.2+

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

Para aquellos que no saben cómo obtener la mayoría de 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
}}

Swift 5+ Answer 'keyWindow' quedó obsoleto en la edición sugerida de iOS 13.0

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

Para aquellos que no saben cómo obtener la mayoría de 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
fuente
0

Descubrí que necesitaba crear una cola para apilar las solicitudes de 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
fuente
-3

Simplemente descarte el controlador actual y presente el que desee, es decir

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

self.displayAlertController()

Idelfonso Gutiérrez
fuente