Jeremie
Jeremie

Reputation: 1

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

I am fairly new to Swift but I am creating a app that requires after a certain time to enter either Touch-ID or a PIN. I am checking a timer from AppDelegate.swift to see if it has expired and if it has expired I am making a call to my "BaseTableViewController" which holds my function authenticateUser. Again I am calling this from my AppDelegate.swift file by creating an instance of BaseTableViewController var baseTableVC = BaseTableViewController() and making a call if timer expired to self.baseTableVC.authenticateUser().

Anyways I am getting: Warning: Attempt to present <UIAlertController: 0x7fed5ae1dcf0> on <admin.BaseViewController: 0x7fed5ad279d0> whose view is not in the window hierarchy!

Thank you in advance for you help!

    func showPasswordAlert(){
    let alertController = UIAlertController(title: "Touch ID Password", message: "Please enter your password", preferredStyle: .Alert)
    let defaultAction = UIAlertAction(title: "OK", style: .Cancel) {(action) -> Void in
        if let textField = alertController.textFields?.first as UITextField?{
            if textField.text == "hello" {
                print("Authentication successfull!")
            }
            else{
                self.showPasswordAlert()
            }
        }

    }
    alertController.addAction(defaultAction)
    alertController.addTextFieldWithConfigurationHandler{(textField) -> Void in
        textField.placeholder = "Password"
        textField.secureTextEntry = true

    }
    presentViewController(alertController, animated: true, completion: nil)

}

func authenticateUser(){
    let context = LAContext()
    var error: NSError?
    let reasonString = "Authentication is required for Admin!"
    context.localizedFallbackTitle = "Enter your PIN Code"

    if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error){
        context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: {(success, policyError) ->Void in
            if success{
                print("Authentication successful!")
            }
            else{
                switch policyError!.code{
                case LAError.SystemCancel.rawValue:
                    print("Authentication was cancelled by the system!")
                case LAError.UserCancel.rawValue:
                    NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
                        self.showPasswordAlert()

                    })
                    print("Authentication was cancelled by the user!")
                case LAError.UserFallback.rawValue:
                    print("User selected to enter password.")
                    NSOperationQueue.mainQueue().addOperationWithBlock({() -> Void in
                        self.showPasswordAlert()
                    })
                default:
                    print("Authentication failed!")
                    NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
                        self.showPasswordAlert()
                    })
                }
            }
        })
    }
    else{
        print(error?.localizedDescription)
        NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
            self.showPasswordAlert()
        })
    }

var baseTableVC = BaseTableViewController()

func applicationWillEnterForeground(application: UIApplication) {
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    let logInStatus = NSUserDefaults.standardUserDefaults()
    let currentTime = NSDate().timeIntervalSince1970
    let roundCurrentTime = (round(currentTime))
    // Pin expire limit
    let pinExpLimit: Double = 30
    // Set the exact time of expire for pin
    let pinExpDate = (currentTime + pinExpLimit)
    let newPinExpDate = (round(pinExpDate))
    if (logInStatus.doubleForKey("expPinTime") <= roundCurrentTime) {
        self.baseTableVC.authenticateUser()
        print("AppDelegate Pin Exp Time")
        print(logInStatus.doubleForKey("expPinTime"))
        //print(newPinExpDate)
        print("AppDelegate Current Time")
        print(roundCurrentTime)
        logInStatus.setDouble(newPinExpDate, forKey: "expPinTime")
        NSUserDefaults.standardUserDefaults().synchronize()

    }
}

Upvotes: 0

Views: 771

Answers (2)

user3237732
user3237732

Reputation: 2028

You can't create instance of view controller just by calling default constructor, use storyboard. Correct me if I'm wrong

Upvotes: 0

coder.pm
coder.pm

Reputation: 81

I suspect you simply create an instance of BaseTableViewController but you don't add its view to the view hierarchy before presenting the UIAlertController's instance.

If self.baseTableVC is the root view controller of your app, then a call like this

baseTableVC.presentViewController(instanceOfUIAlertController, animated: true, completion: yourCompletionBlock)

should work from within the AppDelegate. If self.baseTableVC is not the root view controller, then or you make sure to invoke the previous command on the root VC of your app

window.rootViewController.presentViewController(instanceOfUIAlertController, animated: true, completion: yourCompletionBlock)

or make sure you embed the view of self.baseTableVC in the view hierarchy and then call

baseTableVC.presentViewController(instanceOfUIAlertController, animated: true, completion: yourCompletionBlock)

As a side note, if your alert must be displayed from anywhere in the app, then your approach is ok. If instead your alert must be displayed only from a specific screen, I would remove the timer logic from the app delegate and move it inside the presenting view controller. This would keep your app delegate clean from unnecessary code and would confine the control logic in the right place: the presenting view controller

Upvotes: 1

Related Questions