Reputation: 1
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
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
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