TouchID canceling should dismiss the view controller - ios

For my app I need to save one page of it with TouchID. So the user is kinda forced to use TouchID or if the device does not support it, a password. If the user cancels the TouchID authentication, I want to View to disappear and get back to the root view. I already had that working, but somehow it does not work anymore and I really don't know why?! I just copied until the canceled option, the rest is does not matter I guess.
func authenticateUser() {
let context = LAContext()
var error: NSError?
let reasonString = "Authentication is needed to access your App"
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error){
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: { (success, policyError) -> Void in
if success {
print("authentification successful")
}
}else{
switch policyError!.code{
case LAError.SystemCancel.rawValue:
print("Authentification was canceled by the system")
case LAError.UserCancel.rawValue:
print("Authentication was canceled by user")
self.navigationController?.dismissViewControllerAnimated(true, completion: nil)
//Yes I also tried popToRootViewController, still doesn't work
}

The documentation for the evaluatePolicy call says:
"Reply block that is executed when policy evaluation finishes. This block is evaluated on a private queue internal to the framework in an unspecified threading context."
So the problem is that you are trying to call navigation from the wrong thread. You need to make that call on the UI thread. For example:
dispatch_async(dispatch_get_main_queue()) {
// Navigation goes here
}

Related

FaceID/TouchID success case keeps prompting for further authentication

I've implemented password/TouchID/FaceID on a view controller and when I hit the success case, I'd expect the prompt to stop firing but it just fires over and over again.
In my VC:
var context: LAContext!
func authenticateReturningUser() {
context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
let reason = "Verify that this is your device to continue."
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, error in
DispatchQueue.main.sync {
guard success else {
guard let error = error else {
// show error
return
}
switch error {
case LAError.userCancel:
// do stuff
return
default: return
}
}
print("success")
}
}
}
}
The prompt should fire once and not again if the user successfully authorizes
Edit:
authenticateReturningUser is called from the AppDelegate's applicationDidBecomeActive function:
self.coverVC?.completionHandler = { self.removeBackgroundVC() }
self.coverVC?.authenticateReturningUser()
As far as I remember, when showing the Touch ID prompt, your app becomes inactive. So when the prompt is dismissed, your app becomes active again, triggering the App Delegate's applicationDidBecomeActive again.
You might consider introducing a flag that stores whether the app became inactive because of Touch ID / Face ID etc. or because of another reason and use it in applicationDidBecomeActive to decide if authentication should be triggered or not.
Where are you calling authenticateReturningUser()? You may want to create a static boolean authenticated that if false, allows the call to authenticateReturningUser(), and if true, skips the call, and set authenticated = true after calling the function once.

UITextField Password Autofill Confirmation

I've been playing around with UITextField's password autofill feature for logging into my backend, and as of yet I've been unable to find a way to actually confirm or validate that the user has authenticated via TouchID to access their passwords.
Am I crazy or because this feature is so baked in to iOS, we can't actually check to see if the user was able to successfully authenticate?
Or am I missing some kind of delegate call in the LocalAuthentication API that gets called?
TIA for your help.
I use a method like this with a callback with the result, sometimes I store off the result to look up later on in the session. Not sure if this is what you're looking for, or if you needed something more advanced. This is part of a class for me where I have made my own delegates that I call on authentication or failure as well.
private func authenticateUser(completion: #escaping (Bool)->()) {
let context = LAContext()
var error:NSError?
let reason = "Authenticate for access"
context.localizedFallbackTitle = ""
if(context.canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: &error)){
context.evaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason, reply: { (success, error) -> Void in
if(success){
completion(true)
} else {
completion(false)
}
})
}
}

Face ID auth freezes application after canEvaluatePolicy call

In my application, I attempt to login with Face ID using evaluatePolicy, and after 1 or 2 logins, I get an error in the callback. In that error, I call an async completion handler which calls another and eventually it calls canEvaluatePolicy. My app then freezes, allowing no further interaction with the UI. Why would this be happening? Does it have something to do with thread-safety? Some sample code that recreates this issue, can be found here
Here is a code snippet from that sample code for reference:
let localAuthenticationContext = LAContext()
if localAuthenticationContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) {
localAuthenticationContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "To access secure data") { success, evaluateError in
if success {
print("Success")
} else {
print("Face ID Error")
let context = LAContext()
let status = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
print("Status: \(status)")
}
}
}
I've been using Xcode 9.1 Beta 2 with the iPhone X simulator.

How to use passcode lock scene in my app?

Actually, I building an app which contains local authentication.
My code so far:
func authenticateUser() {
let authenticationContext = LAContext()
var error: NSError?
let reasonString = "Touch the Touch ID sensor to unlock."
// Check if the device can evaluate the policy.
if authenticationContext.canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: &error) {
authenticationContext.evaluatePolicy( .deviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: { (success, evalPolicyError) in
if success {
print("success")
} else {
if let evaluateError = error as NSError? {
// enter password using system UI
}
}
})
} else {
print("toch id not available")
// enter password using system UI
}
}
My problem is I want to use the passcode lock scene when the app doesn't has touch ID or invalid finger print.
Like below Image:
How can I do it?
You should use .deviceOwnerAuthentication instead of .deviceOwnerAuthenticationWithBiometrics to evaluate policy. With this parameter the system uses biometric authentication if available else it presents passcode screen. And if the biometric authentication is available but fails, a fallback button redirect to the passcode screen. See documentation :
If Touch ID or Face ID is available, enrolled, and not disabled, the user is asked for that first. Otherwise, they are asked to enter the device passcode.
Tapping the fallback button switches the authentication method to ask the user for the device passcode.
So your code will be:
func authenticateUser() {
let authenticationContext = LAContext()
var error: NSError?
let reasonString = "Touch the Touch ID sensor to unlock."
// Check if the device can evaluate the policy.
if authenticationContext.canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: &error) {
authenticationContext.evaluatePolicy( .deviceOwnerAuthentication, localizedReason: reasonString, reply: { (success, evalPolicyError) in
if success {
print("success")
} else {
// Handle evaluation failure or cancel
}
})
} else {
print("passcode not set")
}
}
At this point, I am afraid that you cannot access this passcode lock screen into your app, it is related to the iOS itself. You might need to build your own custom view controller to look/behave as the passcode lock scene (with Touch ID). I would suggest to use a library for achieving this, personally, I've tried PasscodeLock and it works fine for me.

Parse.com PFUser signUpInBackgroundWithBlock: block not being called on first tap

i am developing an iOS-App with Swift and the parse.com framework and have a big problem with registering new users.
The block of "signUpInBackgroundWithBlock" is not being called on the first tap, although the new user is getting registered. When i am tapping the button a second time, the block gets finally called and i get an error, that the username is already registered.
var newUser = PFUser()
newUser.username = registerView.nicknameTextField.text.trim()
newUser.email = registerView.emailTextField.text
newUser.password = registerView.passwordTextField.text
newUser.signUpInBackgroundWithBlock {
(succeeded: Bool, error: NSError!) -> Void in
self.registerCompletionBlock(succeeded, error: error)
}
Is someone having the same problem and knows a solution for this strange behaviour?
Thanks!
Edit:
The completion block should call the "registerCompletionBlock()" function:
func registerCompletionBlock(succeeded: Bool, error: NSError!) {
if error == nil {
let subscriptionStoryboard = UIStoryboard(name: "Subscription", bundle: nil)
let viewcontroller: UIViewController = subscriptionStoryboard.instantiateInitialViewController() as UIViewController
self.presentViewController(viewcontroller, animated: true, completion: nil)
} else {
if let errorString = error.userInfo?["error"] as? NSString {
println(errorString)
if error.userInfo?["code"] as Float == 202{
let alert = UIAlertView(title: "vergeben", message: "name vergeben", delegate: nil, cancelButtonTitle: "abbrechen")
alert.show()
}
}
}
}
I tried the solution posted previously (eliminating PFUser.enableAutomaticUser()), and the issue persisted.
In case anyone else is still looking for a solution to this issue, try changing the if error == nil to if succeeded == true in the block of signUpInBackground. This worked for me and all is functional in backend.
Its calling at first time, but its taking a little time for calling this .. because its sending data on server in asynchronous. Asynchronous never block the main thread..
Because : -
Asynchronous never block the main thread waiting for a network response.
Asynchronous can be either synchronous on a separate thread, or scheduled in the run loop of any thread.
Asynchronous can be either synchronous on a separate thread, or scheduled in the run loop of any thread.
Synchronous blocks main thread until they complete request.
Because you call the method asynchronous, it takes some time to do it and your main thread doesn't wait for the method to finish. So you should, if you want to show a message or perform a segue, after the registration, put it inside the completion-block:
newUser.signUpInBackgroundWithBlock {
(succeeded: Bool!, error: NSError!) -> Void in
if error == nil {
// Perform a segue, show a message or whatever you want
} else {
let errorString = error.userInfo["error"] as NSString
// Show the errorString somewhere and let the user try again.
}
}
Also if you don't want to do it asynchronous, you can call the signUp() method(without the inBackgroundWithBlock. That way the application waits for the signup to finish until it continues.
i figured it out. Problem was, that i used PFUser.enableAutomaticUser() in the AppDelegate. I removed that line and the signup-block works flawlessly now!

Resources