iOS 11 Simulator not allowing LAContext and FaceID - ios

I've running the latest Xcode 9 GM (13 Sep 2017) and have set Hardware > Face ID > Enrolled in simulator as well as Deployment Target 11.0. However I'm getting error code -6 LAErrorTouchIDNotAvailable.
Is there some setting I'm missing?
let myContext = LAContext()
let myLocalizedReasonString = "You are pretty"
var authError: NSError?
if #available(iOS 8.0, macOS 10.12.1, *) {
if myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, evaluateError in
if success {
print("// User authenticated successfully, take appropriate action")
} else {
print(" // User did not authenticate successfully, look at error and take appropriate action")
}
}
} else {
print(" // Could not evaluate policy; look at authError and present an appropriate message to user")
}
} else {
print(" // Fallback on earlier versions")
}

Face ID does not work in the Xcode 9 GM due to a framework bug. Xcode 9.1 fixes this issue.
You can test your app in the iPhone 8 simulator and ensure it works correctly with Touch ID or run the Xcode 9.1 beta and test Face ID support there.

I think the iphone X simulator's faceID doesn't work at the moment, hopefully they will fix it soon...
https://forums.developer.apple.com/thread/86779
we could do a bug report to see if it speed things along :P
https://developer.apple.com/bug-reporting

Face ID is working now, with Xcode 9.1. Follow these steps to test it in Simulator.
Add privacy statement in your target's info.plist file.
Import LocalAuthentication framework to your project and add following code to your view controller and try with Face-ID
import LocalAuthentication
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
localAuthentication()
}
func localAuthentication() -> Void {
let laContext = LAContext()
var error: NSError?
let biometricsPolicy = LAPolicy.deviceOwnerAuthenticationWithBiometrics
if (laContext.canEvaluatePolicy(biometricsPolicy, error: &error)) {
if let laError = error {
print("laError - \(laError)")
return
}
var localizedReason = "Unlock device"
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 {
print("No Biometric support")
}
} else {
// Fallback on earlier versions
}
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")
}
}
})
})
}
}
}
FaceID authentication will prompt you for first time to allow FaceID detection for your app.
Now enable Face ID enrolment and run your app to test Face ID simulation Testing.
Here is simulation result for matching and non-matching faces.
Result for matching face:
Result for non-matching face:

XCode 9.1 beta came out today in which the original code should work perfectly in the simulator!

According to Apples Documentation for LAContext, we need to add the key NSFaceIDUsageDescription with a reason of use String, as that will display the authorisation request for the use of FaceId on the device.
Example add this to info.plist:
NSFaceIDUsageDescription
set it to type String, and add a text that you want to be shown, in the prompt request for access to the Face ID camera.
"Your app" request your permission to use Face ID, for you to login to your account / unlock your notes / what ever reason in the end.
By adding this, you can go to the simulator for iPhone X, and you will be prompted for the Face ID, press accept, and everything should work perfectly.
Remember to enrol biometry support for the simulator by going into
Simulator -> Hardware -> Face ID / Touch ID -> Enrolled
Then you just need to pressed the Match / Non-Matching Touch / Face ID, to test out your handling
For more details and check out Apple's documentation: https://developer.apple.com/documentation/localauthentication/lacontext
---- Edit ----
This worked for me in both Xcode 9.0 and 9.1

Related

Xcode simulator does not show Face ID biometrics prompt

`Hi everyone, I am new to Swift and following a biometrics authentication tutorial to click a button and use Face ID or Touch ID.
Nothing happens when the biometric authentication button is pressed because canEvaluate etc do not get set to true. Details below.
variables and the relevant function
private(set) var context = LAContext()
#Published private(set) var biometryType: LABiometryType = .none
private(set) var canEvaluatePolicy = false
#Published private(set) var isAuthenticated = false`
func getBiometryType(){
context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
biometryType = context.biometryType
}
func authenticatedWithBiometrics() async {
context = LAContext()
print("inside auth func")
if canEvaluatePolicy{
let reason = "to log in to your account"
do{
let success = try await context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason)
print(success)
if success {
DispatchQueue.main.async {
self.isAuthenticated = true
print("is authenticated", self.isAuthenticated)
}
}
}
catch{ //if fail to authenticate, throw an error, show it, no biometry
print(error.localizedDescription)
DispatchQueue.main.async {
self.errorDescription = error.localizedDescription
self.showAlert = true
self.biometryType = .none
}
}
}
}
The issue: nothing happens when I click on the button that calls authenticatedwithbiometrics(). The function is called, the issue is that canEvaluatePolicy is FALSE and does not get set to true in getBiometryType. If I manually configure variables canEvaluatePolicy and success to true, we eventually run into the error.localizedDescription -> "biometry is not enrolled".
I've tried different simulators: iPhone 13, 14, 14 Pro, 14Pro Max, and SE which uses TouchID, and that also does nothing.
I've seen someone who had a same problem, but then it was solved by trying different simulators. That did not solve my issue.
I don't think it's an issue with the code because it's literally copied from a working tutorial. Do I need to configure something in the simulator?
Can we test Face ID in simulator? this type of answer is not useful to me because I cannot even evoke the gray popup that initiates the face or touch ID process.
I am using the newest version of xcode.
Thank you. `
I know why now. You have to restart the build after checking the "Enrolled face" button for the cue to show up.
You can used biometric authentication with simulator but you have to ensure you check enrolled in feature > FaceId > Enrolled after this you can check both scenarios Matching or Non Matching

iOS 13: Using the new NEHotspotConfiguration.init(ssidPrefix: String) does not seem to work

I am currently running Xcode 11.0 and iOS 13.1 (beta). I am experimenting with the newly added functionality in iOS 13 of being able to connect to Wifi hotspots where only the prefix is known:
Apple Docs
This is perfect for headless accessory's Wifi setup, as you would not need to ask the user to switch to the OS settings in order to connect to the accessory's wifi.
But unfortunately I cannot make it work as expected.
My code (Swift 5):
if #available(iOS 13, *) {
// The accessory's wifi name starts with "device-", followed by 3 digit number, e.g. "device-012"
let configuration = NEHotspotConfiguration.init(ssidPrefix: "device-")
configuration.joinOnce = true
NEHotspotConfigurationManager.shared.apply(configuration) { (error) in
if error != nil {
if error?.localizedDescription == "already associated."
{
print("Connected")
}
else {
print("No Connected")
}
}
else {
print("Connected")
}
}
}
Using the full name (e.g. "device-012"), it works:
let configuration = NEHotspotConfiguration.init(ssidPrefix: "device-012")
Am I missing something? Does the prefix-string maybe needs some wildcard pattern or so?
Thanks,
Henry
Not setting configuration.joinOnce = true or setting it to false makes it work.
A bug-report is already filed to Apple.

Why evaluatePolicy fails only once if TouchID is turn off for iPhone Unlock

I want to use TouchID in my app and I've found some weird behaviour. So in general when TouchID alert is shown it and user try to authenticate with fingerprint canEvaluatePolicy will fail only after 3 attempts but if user go to Settings>Touch ID & Passcode and turn off "use touch id for: iPhone Unlock" canEvaluatePolicy will fail after first attempt with error message "Biometry is disabled for unlock.". Does anyone know is it a bug or it is by design.
Also looks like it happens only on iOS 11.
Here is code that i use for TouchID configuration
var error: NSError?
context.localizedFallbackTitle = "fallback title"
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
DispatchQueue.main.async {
if success {
// success case
}
else {
// error handling
}
}
}
}
else {
// error handling
}

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.

Determine if an iOS device supports TouchID without setting passcode

I'm currently developing an iOS app that enables users to log in to the app using TouchID, but firstly they must set up a password inside the app first. Problem is, to show the setup password option to enable the TouchID login, I need to detect if the iOS device supports TouchID.
Using the LAContext and canEvaluatePolicy (like the answers in here If Device Supports Touch ID), I am able to determine whether the current device supports TouchID if the user has set up passcode on their iOS device. Here is a my code snippet (I'm using Xamarin, so it's in C#):
static bool DeviceSupportsTouchID ()
{
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
var context = new LAContext();
NSError authError;
bool touchIDSetOnDevice = context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out authError);
return (touchIDSetOnDevice || (LAStatus) Convert.ToInt16(authError.Code) != LAStatus.TouchIDNotAvailable);
}
return false;
}
If the user has not set up the device passcode, the authError will just return "PasscodeNotSet" error regardless of whether the device actually supports TouchID or not.
If the user's device supports TouchID, I want to always show the TouchID option in my app regardless of whether the user has set up passcode on their device (I will just warn the user to setup passcode on their device first). Vice versa, if the user's device doesn't support TouchID, I obviously don't want to show the TouchID option in my app.
So my question is, is there a nice way to consistently determine whether an iOS device supports TouchID regardless of whether the user has set up passcode on their device?
The only workaround I can think of is to determine the architecture of the device (which is answered in Determine if iOS device is 32- or 64-bit), as TouchID is only supported on devices with 64-bit architecture. However, I'm looking if there's any nicer way to do this.
In conclusion of the discussion below, for the time being it is not possible to determine whether a device actually supports TouchID or not when the user hasn't set up passcode on their device.
I have reported this TouchID flaw on the Apple bug reporter. Those who want to follow the issue can see it on Open Radar here: http://www.openradar.me/20342024
Thanks #rckoenes for the input :)
EDIT
Turns out that someone has reported a similar issue already (#18364575). Here is Apple's reply regarding the issue:
"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. "
So..... the final answer from Apple is No. :(
Note: similar StackOverflow question from the person who reported this -> iOS8 check if device has Touch ID
(wonder why I didn't find this question before despite my extensive searching...)
The correct way to detect if TouchID is available:
BOOL hasTouchID = NO;
// if the LAContext class is available
if ([LAContext class]) {
LAContext *context = [LAContext new];
NSError *error = nil;
hasTouchId = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error];
}
Sorry it is in Objective-C, you might have to translate it to C#.
You should refrain from checking the system version and just check whether or not the class or methods are available.
I know this is a question from last year, but this solution does not make what you need? (Swift code)
if #available(iOS 8.0, *) {
var error: NSError?
let hasTouchID = LAContext().canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error)
//Show the touch id option if the device has touch id hardware feature (even if the passcode is not set or touch id is not enrolled)
if(hasTouchID || (error?.code != LAError.TouchIDNotAvailable.rawValue)) {
touchIDContentView.hidden = false
}
}
Then, when the user presses the button to log in with touch id:
#IBAction func loginWithTouchId() {
let context = LAContext()
var error: NSError?
let reasonString = "Log in with Touch ID"
if (context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error)) {
[context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: { (success: Bool, evalPolicyError: NSError?) -> Void in
//Has touch id. Treat the success boolean
})]
} else {
//Then, if the user has touch id but is not enrolled or the passcode is not set, show a alert message
switch error!.code{
case LAError.TouchIDNotEnrolled.rawValue:
//Show alert message to inform that touch id is not enrolled
break
case LAError.PasscodeNotSet.rawValue:
//Show alert message to inform that passcode is not set
break
default:
// The LAError.TouchIDNotAvailable case.
// Will not catch here, because if not available, the option will not visible
}
}
}
Hope it helps!
For Objective C
It works great on all devices without checking device version.
- (void)canAuthenticatedByTouchID{
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *myLocalizedReasonString = touchIDRequestReason;
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
}else{
switch (authError.code) {
case kLAErrorTouchIDNotAvailable:
[labelNotSupportTouchID setHidden:NO];
[switchBtn setHidden:YES];
[labelEnableTouchid setHidden:YES];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self showAlertMessage:#"EyeCheck Pro" message:#"Device does not support Touch ID Service."];
});
break;
}
}
}
Here is a bit tedious way to figure out if device has physical touch id sensor.
+ (BOOL)isTouchIDExist {
if(![LAContext class]) //Since this mandotory class is not there, that means there is no physical touch id.
return false;
//Get the current device model name
size_t size;
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
char *model = malloc(size);
sysctlbyname("hw.machine", model, &size, NULL, 0);
NSString *deviceModel = [NSString stringWithCString:model encoding:NSUTF8StringEncoding];
//Devices that does not support touch id
NSArray *deviceModelsWithoutTouchID = [[NSArray alloc]
initWithObjects:
#"iPhone1,1", //iPhone
#"iPhone1,2", //iPhone 3G
#"iPhone2,1", //iPhone 3GS
#"iPhone3,1", //iPhone 4
#"iPhone3,2",
#"iPhone3,3",
#"iPhone4,1", //iPhone 4S
#"iPhone5,1", //iPhone 5
#"iPhone5,2",
#"iPhone5,3", //iPhone 5C
#"iPhone5,4",
#"iPod1,1", //iPod
#"iPod2,1",
#"iPod3,1",
#"iPod4,1",
#"iPod5,1",
#"iPod7,1",
#"iPad1,1", //iPad
#"iPad2,1", //iPad 2
#"iPad2,2",
#"iPad2,3",
#"iPad2,4",// iPad mini 1G
#"iPad2,5",
#"iPad2,5",
#"iPad2,7",
#"iPad3,1", //iPad 3
#"iPad3,2",
#"iPad3,3",
#"iPad3,4", //iPad 4
#"iPad3,5",
#"iPad3,6",
#"iPad4,1", //iPad Air
#"iPad4,2",
#"iPad4,3",
#"iPad4,4", //iPad mini 2
#"iPad4,5",
#"iPad4,6",
#"iPad4,7",
nil];
return ![deviceModelsWithoutTouchID containsObject:deviceModel];
}
Reference:
https://www.theiphonewiki.com/wiki/Models
https://en.wikipedia.org/wiki/IOS
Following is the way by which you can identify whether Touch Id or Face ID is supported on the device
open class LocalAuth: NSObject {
public static let shared = LocalAuth()
private override init() {}
var laContext = LAContext()
func canAuthenticate() -> Bool {
var error: NSError?
let hasTouchId = laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
return hasTouchId
}
func hasTouchId() -> Bool {
if canAuthenticate() && laContext.biometryType == .touchID {
return true
}
return false
}
func hasFaceId() -> Bool {
if canAuthenticate() && laContext.biometryType == .faceID {
return true
}
return false
}
}
And following is the Usage of the above-shared code
if LocalAuth.shared.hasTouchId() {
print("Has Touch Id")
} else if LocalAuth.shared.hasFaceId() {
print("Has Face Id")
} else {
print("Device does not have Biometric Authentication Method")
}
For iOS 11+ you can use biometryType: LABiometryType of LAContext. More from Apple documentation:
/// Indicates the type of the biometry supported by the device.
///
/// #discussion This property is set only when canEvaluatePolicy succeeds for a biometric policy.
/// The default value is LABiometryTypeNone.
#available(iOS 11.0, *)
open var biometryType: LABiometryType { get }
#available(iOS 11.0, *)
public enum LABiometryType : Int {
/// The device does not support biometry.
#available(iOS 11.2, *)
case none
/// The device does not support biometry.
#available(iOS, introduced: 11.0, deprecated: 11.2, renamed: "LABiometryType.none")
public static var LABiometryNone: LABiometryType { get }
/// The device supports Touch ID.
case touchID
/// The device supports Face ID.
case faceID
}
For iOS 11+, for context error, you can check for kLAErrorBiometryNotAvailable

Resources