Attempt to present UIAlertController on ViewController whose view is not in the window hierarchy!

Hi there team.

I have this issue, it makes things a little slower in runnning conditions


i have a QR scanner


func metadataOutput (_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        if metadataObjects != nil && metadataObjects.count != 0 {
            if let object = metadataObjects[0] as? AVMetadataMachineReadableCodeObject {
                if object.type == AVMetadataObject.ObjectType.qr {
                   //this is where the problem starts
                    let alert = UIAlertController(title: "Your code is:", message: object.stringValue, preferredStyle: .alert)
                   
                    if object.stringValue == "1"{
                        self.performSegue(withIdentifier: "sendToAsistente", sender: nil)
                    }
                    else if object.stringValue == "2"{
                        self.performSegue(withIdentifier: "sendToExpositor", sender: nil)
                    }
                    else{
                      print("nada jeje")
                    }
                    
                    
                    alert.addAction(UIAlertAction(title: "Retake", style: .default, handler: nil))
                    alert.addAction(UIAlertAction(title: "Copy", style: .default, handler: { (nil) in
                        UIPasteboard.general.string = object.stringValue
                    }))
                    self.present(alert, animated: true, completion: nil)
                    }
            }
        }

    }



my Alert is making this issue happend

et object = metadataObjects[0] as? AVMetadataMachineReadableCodeObject {
                if object.type == AVMetadataObject.ObjectType.qr {
                   //this is where the problem starts
                    let alert = UIAlertController(title: "Your code is:", message: object.stringValue, preferredStyle: .alert)
                   
                    if object.stringValue == "1"{
                        self.performSegue(withIdentifier: "sendToAsistente", sender: nil)
                    }
                    else if object.stringValue == "2"{
                        self.performSegue(withIdentifier: "sendToExpositor", sender: nil)
                    }
                    else{


i have tried to

DispatchQueue.main.async {
}

but it didn't work

if object.type == AVMetadataObject.ObjectType.qr {
                   
                    let alert = UIAlertController(title: "Your code is:", message: object.stringValue, preferredStyle: .alert)
                   
                    if object.stringValue == "1"{
                        DispatchQueue.main.async {
                        self.performSegue(withIdentifier: "sendToAsistente", sender: nil)
                        }
                    }
                    else if object.stringValue == "2"{
                        DispatchQueue.main.async {
                        self.performSegue(withIdentifier: "sendToExpositor", sender: nil)
                        }
                    }

gives me this error :

Warning: Attempt to present ContainerViewController: 0x145e4020> on ViewController: 0x146818b0> which is already presenting <UIAlertController: 0x14957a00>



that's the error what i'm missing?


whole code


import UIKit
import AVFoundation

class RegistroViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
  //
    //
    
    @IBOutlet weak var square: UIImageView!
    var video = AVCaptureVideoPreviewLayer()
    var alertController: UIAlertController?
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        //Creating session
        let session = AVCaptureSession()
        
        //Define capture devcie
        let defaultDevice = AVCaptureDevice.default(for: AVMediaType.video)
        
        do
        {
            let input = try AVCaptureDeviceInput(device: defaultDevice!)
            session.addInput(input)
        }
        catch
        {
            print ("ERROR")
        }
        
        let output = AVCaptureMetadataOutput()
        session.addOutput(output)
        
        output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
        
        output.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]
        
        video = AVCaptureVideoPreviewLayer(session: session)
        video.frame = view.layer.bounds
        view.layer.addSublayer(video)
        
        self.view.bringSubviewToFront(square)
        
        session.startRunning()
    }
    

    
    func metadataOutput (_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        if metadataObjects != nil && metadataObjects.count != 0 {
            if let object = metadataObjects[0] as? AVMetadataMachineReadableCodeObject {
                if object.type == AVMetadataObject.ObjectType.qr {
                   
                    let alert = UIAlertController(title: "Your code is:", message: object.stringValue, preferredStyle: .alert)
                   
                    if object.stringValue == "1"{
                        self.performSegue(withIdentifier: "sendToAsistente", sender: nil)
                    }
                    else if object.stringValue == "2"{
                        self.performSegue(withIdentifier: "sendToExpositor", sender: nil)
                    }
                    else{
                      print("nada jeje")
                    }
                    
                    
                    alert.addAction(UIAlertAction(title: "Retake", style: .default, handler: nil))
                    alert.addAction(UIAlertAction(title: "Copy", style: .default, handler: { (nil) in
                        UIPasteboard.general.string = object.stringValue
                    }))
                    self.present(alert, animated: true, completion: nil)
                    }
            }
        }

    }
    
    
//laaaaaaassssstttt
}

this is the error

Warning: Attempt to present <UIAlertController: 0x183c3a00> on <Conappeme.RegistroViewController: 0x17e7fc70> whose view is not in the window hierarchy!

Accepted Reply

The problem is that the delegate method is called more that one, so you need to ensure that there is not already an alertController presented before present another one.


Also you don't need to use:


DispatchQueue.main.async {  
} 


Because you indicated that you would like to use the main queue for the delegate methods:


captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)


The following code take into account if there is already an alertController presented.


import UIKit
import AVFoundation

class ViewController: UIViewController {

    @IBOutlet weak var squareView: UIImageView!
    var video = AVCaptureVideoPreviewLayer()
    
    var alertController: UIAlertController?

    override func viewDidLoad() {
        super.viewDidLoad()
        //Creating session
        let captureSession = AVCaptureSession()
        
        //Define capture devcie
        let defaultDevice = AVCaptureDevice.default(for: AVMediaType.video)
        
        do {
            let input = try AVCaptureDeviceInput(device: defaultDevice!)
            captureSession.addInput(input)
        }
        catch {
            print ("ERROR")
        }
        
        let captureMetadataOutput = AVCaptureMetadataOutput()
        captureSession.addOutput(captureMetadataOutput)
        
        captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
        
        captureMetadataOutput.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]
        
        video = AVCaptureVideoPreviewLayer(session: captureSession)
        video.frame = view.layer.bounds
        view.layer.addSublayer(video)
        
        view.bringSubviewToFront(squareView)
        
        captureSession.startRunning()
    }
}

extension ViewController : AVCaptureMetadataOutputObjectsDelegate{
    func metadataOutput(_ captureMetadataOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        // if metadataObjects != nil && metadataObjects.count != 0 { // WARNING: Comparing non-optional value of type '[AVMetadataObject]' to nil always returns true
        
        guard metadataObjects.count != 0 else {
            return
        }
        
        guard let object = metadataObjects[0] as? AVMetadataMachineReadableCodeObject else {
            return
        }
        
        guard object.type == AVMetadataObject.ObjectType.qr else {
            return
        }
        
        guard alertController == nil else {
            print("There is already an alert presented")
            return
        }
        
        alertController = UIAlertController(title: "Your code is:", message: object.stringValue, preferredStyle: .alert)

        if object.stringValue == "1"{
            self.performSegue(withIdentifier: "sendToAsistente", sender: nil)
        } else if object.stringValue == "2"{
            self.performSegue(withIdentifier: "sendToExpositor", sender: nil)
        } else{
            print("nada jeje")
        }
        
        guard let alert = alertController else {
            return
        }
        
        alert.addAction(UIAlertAction(title: "Retake", style: .default, handler: { [weak self]  (nil) in
            guard let self = self else { return }
            self.alertController = nil
        }))
        
        alert.addAction(UIAlertAction(title: "Copy", style: .default, handler: { [weak self] (nil) in
            guard let self = self else { return }
            self.alertController = nil
            UIPasteboard.general.string = object.stringValue
        }))
        
        present(alert, animated: true, completion: nil)
    }
}

Replies

That's because you call the dialog from a thread and the containing view is not "there".

Probably also because you present alert during the segue is performing, creating confusion.


You could try presenting the alert before calling perform segue.

Or move the alert in the destination view after segue.


Have a look here on solutions


https://stackoverflow.com/questions/29257670/alertcontroller-is-not-in-the-window-hierarchy

yeah thanks i couldn't understand at all

but now i have clear this


+ viewController

+AlertController (no hierarchy)

+UIAlertController (cannot be presented by alert controller


which is my


self.present(alert, animated: true, completion: nil)


i don't have a alert function I'm stuck i dont know what should i write or do

i have tried to move the code to


override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)


even delete the alert and warning is there still

I understand the problem is in this function


    func metadataOutput (_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { 
        if metadataObjects != nil && metadataObjects.count != 0 { 
            if let object = metadataObjects[0] as? AVMetadataMachineReadableCodeObject { 
                if object.type == AVMetadataObject.ObjectType.qr { 
                    
                    let alert = UIAlertController(title: "Your code is:", message: object.stringValue, preferredStyle: .alert) 
                    
                    if object.stringValue == "1"{ 
                        self.performSegue(withIdentifier: "sendToAsistente", sender: nil) 
                    } 
                    else if object.stringValue == "2"{ 
                        self.performSegue(withIdentifier: "sendToExpositor", sender: nil) 
                    } 
                    else{ 
                      print("nada jeje") 
                    } 
                     
                    alert.addAction(UIAlertAction(title: "Retake", style: .default, handler: nil)) 
                    alert.addAction(UIAlertAction(title: "Copy", style: .default, handler: { (nil) in 
                        UIPasteboard.general.string = object.stringValue 
                    })) 
                    self.present(alert, animated: true, completion: nil) 
                    } 
            } 
        } 
 
    }

When do you want the alert to show ? In all cases ? Only if "nada jeje" ?

If so, I would move the line 6 and lines 18 to 22 inside the else on line 16.


If you want the alert to show for instance after performSegue in cas "1", put the alert in the destination controller of sendToAsistente. Is it the Conappeme.RegistroViewController


NOTA: you said : even delete the alert and warning is there still


Can you show the code without the alert ? Do you get the same error message ?

The problem is that the delegate method is called more that one, so you need to ensure that there is not already an alertController presented before present another one.


Also you don't need to use:


DispatchQueue.main.async {  
} 


Because you indicated that you would like to use the main queue for the delegate methods:


captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)


The following code take into account if there is already an alertController presented.


import UIKit
import AVFoundation

class ViewController: UIViewController {

    @IBOutlet weak var squareView: UIImageView!
    var video = AVCaptureVideoPreviewLayer()
    
    var alertController: UIAlertController?

    override func viewDidLoad() {
        super.viewDidLoad()
        //Creating session
        let captureSession = AVCaptureSession()
        
        //Define capture devcie
        let defaultDevice = AVCaptureDevice.default(for: AVMediaType.video)
        
        do {
            let input = try AVCaptureDeviceInput(device: defaultDevice!)
            captureSession.addInput(input)
        }
        catch {
            print ("ERROR")
        }
        
        let captureMetadataOutput = AVCaptureMetadataOutput()
        captureSession.addOutput(captureMetadataOutput)
        
        captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
        
        captureMetadataOutput.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]
        
        video = AVCaptureVideoPreviewLayer(session: captureSession)
        video.frame = view.layer.bounds
        view.layer.addSublayer(video)
        
        view.bringSubviewToFront(squareView)
        
        captureSession.startRunning()
    }
}

extension ViewController : AVCaptureMetadataOutputObjectsDelegate{
    func metadataOutput(_ captureMetadataOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        // if metadataObjects != nil && metadataObjects.count != 0 { // WARNING: Comparing non-optional value of type '[AVMetadataObject]' to nil always returns true
        
        guard metadataObjects.count != 0 else {
            return
        }
        
        guard let object = metadataObjects[0] as? AVMetadataMachineReadableCodeObject else {
            return
        }
        
        guard object.type == AVMetadataObject.ObjectType.qr else {
            return
        }
        
        guard alertController == nil else {
            print("There is already an alert presented")
            return
        }
        
        alertController = UIAlertController(title: "Your code is:", message: object.stringValue, preferredStyle: .alert)

        if object.stringValue == "1"{
            self.performSegue(withIdentifier: "sendToAsistente", sender: nil)
        } else if object.stringValue == "2"{
            self.performSegue(withIdentifier: "sendToExpositor", sender: nil)
        } else{
            print("nada jeje")
        }
        
        guard let alert = alertController else {
            return
        }
        
        alert.addAction(UIAlertAction(title: "Retake", style: .default, handler: { [weak self]  (nil) in
            guard let self = self else { return }
            self.alertController = nil
        }))
        
        alert.addAction(UIAlertAction(title: "Copy", style: .default, handler: { [weak self] (nil) in
            guard let self = self else { return }
            self.alertController = nil
            UIPasteboard.general.string = object.stringValue
        }))
        
        present(alert, animated: true, completion: nil)
    }
}

yes i'm geting the same error

Warning: Attempt to present <Conappeme.AsistenteContainerViewController> on <Conappeme.RegistroViewController> whose view is not in the window hierarchy!




this is how it looks like without alerts

func metadataOutput (_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        if metadataObjects != nil && metadataObjects.count != 0 {
            if let object = metadataObjects[0] as? AVMetadataMachineReadableCodeObject {
                if object.type == AVMetadataObject.ObjectType.qr {
                   
                   
                   
                    if object.stringValue == "1"{
                        self.performSegue(withIdentifier: "sendToAsistente", sender: nil)
                    }
                    else if object.stringValue == "2"{
                        self.performSegue(withIdentifier: "sendToExpositor", sender: nil)
                    }
                   
                   
                   
                  
                }
            }
        }
       
    }

im trying to performSegue to :

when QR code is 1

AsistenteContainerViewController


and QR code 2

UserContainerViewController


also tried moving the alerts

same error


func metadataOutput (_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        if metadataObjects != nil && metadataObjects.count != 0 {
            if let object = metadataObjects[0] as? AVMetadataMachineReadableCodeObject {
                if object.type == AVMetadataObject.ObjectType.qr {
                   
                   
                   
                    if object.stringValue == "1"{
                        self.performSegue(withIdentifier: "sendToAsistente", sender: nil)
                    }
                    else if object.stringValue == "2"{
                        self.performSegue(withIdentifier: "sendToExpositor", sender: nil)
                    }
                    else{
                        print("nada jeje")
                        let alert = UIAlertController(title: "Your code is:", message: object.stringValue, preferredStyle: .alert)
                        alert.addAction(UIAlertAction(title: "Retake", style: .default, handler: nil))
                        alert.addAction(UIAlertAction(title: "Copy", style: .default, handler: { (nil) in
                            UIPasteboard.general.string = object.stringValue
                        }))
                        self.present(alert, animated: true, completion: nil)
                       
                    }
                   
                   
                  
                }
            }
        }
       
    }

i only want to show the alert when QR code is Unknow "else"

thanks for answer but that's by far not understandable to my knowledge 😕

I will try to explain what I think is happening.


class UIAlertController : UIViewController


UIAlertController is a subclass of UIViewController, when you do:


present(alert, animated: true, completion: nil)

The ViewController that presented the UIAlertController can not present more UIViewControllers until the UIAlertController is dismmis.


Did you tried my previous code?

ohhhh thanks

i tried your code and doesn't slow anymore my app when try to prepareSegue

i'm only getting this 1 time insted of 20


Warning: Attempt to present <UIAlertController> on <Conappeme.RegistroViewController> whose view is not in the window hierarchy!

There is already an alert presented



and lots of


There is already an alert presented


thanks for answer and thanks for explaining🙂

TMI.


The ususal cause of this error message is when your code is trying to present an alert viewController from the viewController 'self' and self is not the current viewController. Only the current viewController can present a viewController. Try converting this to Swift and using it to present the alert:

    UIViewController* parentController =self.window.rootViewController;
    while( parentController.presentedViewController &&
          parentController != parentController.presentedViewController ){
        parentController = parentController.presentedViewController;
    }
    [parentController presentViewController:alert animated:YES completion:nil];
   

What this does is start with the first viewController (rootViewController) and search forward until it finds the current viewController. Then it presents the alert from that viewController NOT 'self'.

self is a UIViewController.


So what is self.window ? UIViewController has no window property.

Thanks! My bad. The code I quoted allows the App Delegate to present an alert. This is what I should have quoted (can you translate it to Swift?):


        UIViewController* parentController =[[UIApplication sharedApplication]keyWindow].rootViewController;
        while( parentController.presentedViewController &&
              parentController != parentController.presentedViewController ){
            parentController = parentController.presentedViewController;
        }
        [parentController presentViewController:alert animated:YES completion:nil];

Swift translation:


        let alertController = UIAlertController(
            title: NSLocalizedString("Test", comment: "AppDelegate"),
            message: NSLocalizedString("An alert in any case ", comment: "AppDelegate"), preferredStyle: .alert)
        let cancelAction = UIAlertAction(title: NSLocalizedString("Ok", comment: "AppDelegate"), style: .destructive, handler: nil) // affiche Annuler en rouge
        alertController.addAction(cancelAction)

        var parentController = UIApplication.shared.keyWindow?.rootViewController
        while (parentController?.presentedViewController != nil &&
            parentController != parentController!.presentedViewController) {
                parentController = parentController!.presentedViewController
        }
        parentController?.present(alertController, animated:true, completion:nil)


Note: this must be included in AppDelegate, in

func applicationDidBecomeActive

and not in

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { }


to have keyWindow defined.

Thanks. And thanks for pointing out that the keyWindow is not yet defined in didFinishLaunchingWithOptions.

thanks everyone for your help 🙂

Only one change to prevented from crash

let alert = UIAlertController(title: "Image Selection", message: "From where you want to pick this image?", preferredStyle: .actionSheet)

only change preferred style as alert instead actionSheet

let alert = UIAlertController(title: "Image Selection", message: "From where you want to pick this image?", preferredStyle: .alert)