17

LAContext has method to check if device can evaluate touch ID and gives error message. Problem is that same error message "LAErrorPasscodeNotSet" is given by system in two cases:

1) If user has Touch ID, but turned it off in settings (iPhone 5s with iOS8)

2) If device doesn't have Touch ID (iPad with iOS8)

Q: How to check if device supports Touch ID, but haven't turned it on in settings?

Update:

Had created ticket to Apple regarding this bug (ID# 18364575) and received answer:

"Engineering has determined that this issue behaves as intended based on the following information:

If passcode is not set, you will not be able to detect Touch ID presence. Once the passcode is set, canEvaluatePolicy will eventually return LAErrorTouchIDNotAvailable or LAErrorTouchIdNotEnrolled and you will be able to detect Touch ID presence/state.

If users have disabled passcode on phone with Touch ID, they knew that they will not be able to use Touch ID, so the apps don't need to detect Touch ID presence or promote Touch ID based features. "

Krunal
  • 77,632
  • 48
  • 245
  • 261
1ce
  • 283
  • 3
  • 10
  • That response from Engineering reminds of the advice for requesting Location permission (if user has switched it off or not authorized the app, then don't ask again). Trouble is, it presumes familiarity with the features and settings on the part of the user, i.e. that they always would know to turn them on again in Settings. However, in my experience that's wishful thinking... Users forget these things, understandably. – stephent Feb 23 '15 at 19:48
  • 2
    Looks like Apple fixed this: iOS 8.1 iPad 3(no touch) - LAErrorPasscodeNotSet, iOS 8.4 iPad 3(no touch) - LAErrorTouchIDNotAvailable – Uladzimir Aug 07 '15 at 13:19
  • Just out of curiosity, about your app's design: Why would you care whether the device has TouchID hardware support, when the user has specifically disabled it and it is not available to your app anyway? – Nicolas Miari Aug 18 '15 at 05:43

5 Answers5

7

Maybe you could write your own method to check which device are you running on, because if returned error is the same, it would be hard to figure out exactly if Touch ID is supported. I would go with something like this:

int sysctlbyname(const char *, void *, size_t *, void *, size_t);

- (NSString *)getSysInfoByName:(char *)typeSpecifier
{
    size_t size;
    sysctlbyname(typeSpecifier, NULL, &size, NULL, 0);

    char *answer = malloc(size);
    sysctlbyname(typeSpecifier, answer, &size, NULL, 0);

    NSString *results = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding];

    free(answer);
    return results;
}

- (NSString *)modelIdentifier
{
    return [self getSysInfoByName:"hw.machine"];
}

After having the model identifier, I would just check if model identifier equals is one of the models that support Touch ID:

- (BOOL)hasTouchID
{
    NSArray *touchIDModels = @[ @"iPhone6,1", @"iPhone6,2", @"iPhone7,1", @"iPhone7,2", @"iPad5,3", @"iPad5,4", @"iPad4,7", @"iPad4,8", @"iPad4,9" ];

    NSString *model = [self modelIdentifier];

    return [touchIDModels containsObject:model];
}

The array contains all model ID's which support Touch ID, which are:

  • iPhone 5s
  • iPhone 6
  • iPhone 6+
  • iPad Air 2
  • iPad Mini 3

The only downside of this method is that once new devices are released with Touch ID, the model array will have to be updated manually.

Legoless
  • 10,942
  • 7
  • 48
  • 68
  • 1
    Thanks, Legoless. I suggested such solution. But as you noted at the end of message - this is temporary solution, so i should update it ASAP when new device appears. I hope there is another way, without manually updates. – 1ce Sep 24 '14 at 21:50
  • We can easily assume that all new iPhones will also have Touch ID, so we could add some logic that if model of iPhone is higher than 6,x it could return YES. Just need to be careful if iPad gets Touch ID, but updating this once per year should not be a problem after all - besides you are already updating the app for new iOS anyway. Just need to be careful that it is not forgotten. – Legoless Sep 24 '14 at 21:52
  • We know new devices will support Touch ID so we can just check if its iPhone 4S and 5 then we know it doesn't support Touch ID. Because iPhone 4 or bellow can't go to iOS 8 and if its not iOS8 error will be null. This way dont need to update the app if new devices is announced. – Paragon Oct 29 '14 at 20:38
  • That is also true Paragon. Would actually make more sense to check for that. iOS 7 does not support Touch ID either way, but we must still know what the exact question is. Is it either: "Does this device have Touch ID?" or "Can I use Touch ID?". However it is not really certain that all new devices will have Touch ID in the future. – Legoless Oct 30 '14 at 07:14
  • 1
    I ended up using this same solution, even if is not future-proof and not elegant. The oposite workaround is neither future-proof since apple could release more devices without touchID (C-series, ipod touch, ...). Hope they introduce better ways to detect this. – GuillermoMP Oct 30 '14 at 13:15
1

In Swift 3

fileprivate func deviceSupportsTouchId(success: @escaping () -> (), failure: @escaping (NSError) -> ()) {
    let context = LAContext()
    var authError: NSError?
    let touchIdSetOnDevice = context.canEvaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, error: &authError)

    if touchIdSetOnDevice {
        DispatchQueue.main.async {
             success()
        }
    }
    else {
        DispatchQueue.main.async {
             failure(error!)
        }
    }
}

deviceSupportsTouchId() return Success if the device has touch Id capability.

If not then, function will return error, giving you the following error code if touchIDNotEnrolled is not set yet.

LAError.Code.touchIDNotEnrolled.rawValue

You can handle it using this value.

CodeOverRide
  • 4,431
  • 43
  • 36
1

Here you can check, Touch-ID and Face-ID both (with iOS 11+)

Use property biometryType of LAContext to check and evaluate available biometric policy. (For a passcode authentication , when biometric fails, use: LAPolicyDeviceOwnerAuthentication)

Try this and see:

Objective-C:

LAContext *laContext = [[LAContext alloc] init];

NSError *error;


// For a passcode authentication , when biometric fails, use: LAPolicyDeviceOwnerAuthentication
//if ([laContext canEvaluatePolicy: LAPolicyDeviceOwnerAuthentication error:&error]) {
if ([laContext canEvaluatePolicy: LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {    
    if (error != NULL) {
        // handle error
    } else {

        if (@available(iOS 11, *)) {
            if (laContext.biometryType == LABiometryTypeFaceID) {
                //localizedReason = "Unlock using Face ID"
                NSLog(@"FaceId support");
            } else if (laContext.biometryType == LABiometryTypeTouchID) {
                //localizedReason = "Unlock using Touch ID"
                NSLog(@"TouchId support");
            } else {
                //localizedReason = "Unlock using Application Passcode"
                NSLog(@"No biometric support or Denied biometric support");
            }
        } else {
            // Fallback on earlier versions
        }


        [laContext evaluatePolicy: LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:@"Test Reason" reply:^(BOOL success, NSError * _Nullable error) {

            if (error != NULL) {
                // handle error
            } else if (success) {
                // handle success response
            } else {
                // handle false response
            }
        }];
    }
}

Swift:

let laContext = LAContext()
var error: NSError?
let biometricsPolicy = LAPolicy.deviceOwnerAuthentication //LAPolicy.deviceOwnerAuthenticationWithBiometrics

if laContext.isCredentialSet(LACredentialType.applicationPassword) {
    print("Passsword is set")
}

let localizedFallbackTitle = "Unlock Using Device Passcode"
let localizedCancelTitle = "Use Application Passcode"
if (laContext.canEvaluatePolicy(biometricsPolicy, error: &error)) {

    if let laError = error {
        print("laError - \(laError)")
        return
    }



    //print("biometricsPolicy - \(biometricsPolicy.rawValue)")

    UINavigationBar.appearance().tintColor = UIColor.red


    var localizedReason = "My Reason to be displayed on face id prompt"
    if #available(iOS 11.0, *) {
        if (laContext.biometryType == LABiometryType.faceID) {
            //localizedReason = "Unlock using Face ID"
            print("FaceId support")
        } else if (laContext.biometryType == LABiometryType.touchID) {
            //localizedReason = "Unlock using Touch ID"
            print("TouchId support")
        } else {
            //localizedReason = "Unlock using Application Passcode"
            print("No Biometric support")
        }
    } else {
        // Fallback on earlier versions
    }

    laContext.localizedFallbackTitle = localizedFallbackTitle
    laContext.localizedCancelTitle = localizedCancelTitle
    //laContext.localizedReason = "test loc reason"
    laContext.evaluatePolicy(biometricsPolicy, localizedReason: localizedReason, reply: { (isSuccess, error) in

        DispatchQueue.main.async(execute: {

            if let laError = error {
                print("laError - \(laError)")
            } else {
                if isSuccess {
                    print("sucess")
                } else {
                    print("failure")
                }
            }

        })
    })
}
Krunal
  • 77,632
  • 48
  • 245
  • 261
0

Integrating Touch ID

Now we get to the main part of the tutorial… integrating Touch ID with the app. As it turns out, Apple has made some fairly standard code for accessing Touch ID. The code comes from the Local Authentication Framework and is as follows:

LAContext *myContext = [[LAContext alloc] init];

NSError *authError = nil;

NSString *myLocalizedReasonString = @"Used for quick and secure access   to the test app";

 if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics    error:&authError]) {[myContext    evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
              localizedReason:myLocalizedReasonString
                        reply:^(BOOL success, NSError *error) {
          if (success) {
          // User authenticated successfully, take appropriate action
          } 
         else {
            // User did not authenticate successfully, look at error and take appropriate action
         }
    }];
 } 
 else {
// Could not evaluate policy; look at authError and present an appropriate message to user }

Lets take a look at each line to see what it does:

Line 1: Here we create an LAContext object. The LAContext class is responsible for handling the context for the authentication. Put simply, we use an LAContext object to check if a type of authentication is available. In the case of this tutorial, we will later be checking “if” touch ID is an option.

Line 2: We need an NSError so that the LAContext can use it to return if there is an error.

Line 3: We set an NSString with a description that it put on screen to let the user know why the touch ID view has appeared on screen.

Line 5: This is where we put the LAContext constant to use by calling the canEvaluatePolicy: method and sending it an LAPolicy constant as an argument. In this case, we pass LAPolicyDeviceOwnerAuthenticationWithBiometrics. If this fails, either touch ID is not configured on a compatible device, or touch ID is not available on the device… think an iPhone 4S, 5 or 5c running the app. Also, this doesn’t take in to account a device running iOS 7, so if you plan to run finger print authentication on an app, make sure you check that you are working with a compatible device and if not, make other options available such as password on pin code to access the app.

Lines 6, 7 and 8: If the user can authenticate with biometrics we can now call the evaluatePolicy method on our LAContext object. We do this by passing the same constant over, LAPolicyDeviceOwnerAuthenticationWithBiometrics, as well as passing our reason string and then specifying a block for the response to be handled.

We will get either a YES or a NO as a result. If a YES then line 10 is where we put code for a positive response. Likewise, line 12 is where we put our failure code.

Finally on line 15, we have the ELSE statement which runs if line 5 fails the test… i.e., biometrics is not available. We can check the authError pointer to get the reason and present it to the user if needed.

Finally, to get this to not show errors, we need to import the local authentication framework in to our project:

So, lets add this code to our project. Open ViewController.m and at the top, import the local authentication framework.

For more detail visit this link : http://www.devfright.com/touch-id-tutorial-objective-c/

0

You can find out if a device supports Biometric scanning (touchID and faceID) by checking the error code as shown below:

func deviceSupportsBiometricScanning() -> Bool {
    var authError: NSError?
    let _ = LAContext().canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError)
    return authError?.code != kLAErrorBiometryNotAvailable.hashValue
}
Jan-Dawid Roodt
  • 489
  • 5
  • 13