I was using NSFaceIDUsageDescription in my app and it was working. I deleted my app from my device and and re-uploaded (plugging my device into my mac and running from xcode) it and now I don't get the alert that my app would like to use FaceID, how come the alert is not appearing anymore? This is preventing me from using FaceID in my app.
class TouchIDAuth {
let context = LAContext()
func canEvaluatePolicy() -> Bool {
return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}
func authenticateUser(completion: #escaping (NSNumber?) -> Void) {
guard canEvaluatePolicy() else {
completion(0)
return
}
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Logging in with Touch ID") { (success, evaluateError) in
if success {
DispatchQueue.main.async {
completion(nil)
}
} else {
let response: NSNumber
switch evaluateError?._code {
case Int(kLAErrorAuthenticationFailed):
response = 2
case Int(kLAErrorUserCancel):
response = 3
case Int(kLAErrorUserFallback):
response = 4
default:
response = 1
}
completion(response)
}
}
}
}
And when I do this:
let touchMe = TouchIDAuth()
print(touchMe.canEvaluatePolicy())
The print returns false.
Is this an issue with my device? Or with NSFaceIDUsageDescription?
When your device exceeds the limit of incorrect attempts it usually returns false.
Try locking your device, then unlocking with face/touch ID and it starts working again in your app.
It should also return an error code why it is failling for evaluateError
Hope this was your case and solves the issue.
Related
func authenticateBiometry(completion: #escaping ErrorHandler) {
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: " ") { success, error in
guard let error = error else {
if success {
completion(nil)
}
return
}
completion(error)
}
}
But it prompts for touchId/faceId only the first time. What can I do to ask for it for example every time when I tap button? Let's say every 15 seconds.
Just tested locally and it works for me, this is the only way I found. I saw your comment above but I will put an answer here because probably someone will not find it ugly haha :).
I took some time to google some kind of reset method in LAContext class, but didn't find anything.
The solution was to reset the LAContext at the beginning of the method called on button tap:
func authenticateBiometry(completion: #escaping ErrorHandler) {
context = LAContext() //THIS, implying that context is declared as `var`
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: " ") { success, error in
guard let error = error else {
if success {
completion(nil)
}
return
}
completion(error)
}
}
You will be able to prompt face/touch ID on each button click, as soon as one ends.
I am in the process of implementing UMP SDK into my iOS app. I have setup the GDPR and IDFA messages in the Google AdMob dashboard's Privacy and Messaging section. I am having trouble getting the GDPR message to show up. The IDFA and iOS' ATT messages work perfectly.
Below is the code that I am using. I have tested this on both simulator and physical device. Also, I am located in the EU.
static func trackingConsentFlow(completion: #escaping () -> Void) {
let umpParams = UMPRequestParameters()
let debugSettings = UMPDebugSettings()
debugSettings.geography = UMPDebugGeography.EEA
umpParams.debugSettings = debugSettings
umpParams.tagForUnderAgeOfConsent = false
UMPConsentInformation
.sharedInstance
.requestConsentInfoUpdate(with: umpParams,
completionHandler: { error in
if error != nil {
print("MYERROR #1 \(String(describing: error))")
completion()
} else {
let formStatus = UMPConsentInformation.sharedInstance.formStatus
print("FORM STATUS: \(formStatus)")
if formStatus == .available {
loadForm(completion)
} else {
completion()
}
}
})
}
private static func loadForm(_ completion: #escaping () -> Void) {
UMPConsentForm.load(completionHandler: { form, loadError in
if loadError != nil {
print("MYERROR #2 \(String(describing: loadError))")
completion()
} else {
print("CONSENT STATUS: \(UMPConsentInformation.sharedInstance.consentStatus)")
if UMPConsentInformation
.sharedInstance.consentStatus == .required {
guard let rootViewController = UIApplication.shared.currentUIWindow()?.rootViewController else {
return completion()
}
form?.present(from: rootViewController, completionHandler: { dismissError in
if UMPConsentInformation
.sharedInstance.consentStatus == .obtained {
completion()
}
})
}
}
})
}
Just to be clear:
With this code I am able to show the IDFA message, after which the AppTrackingTransparency alert is shown. But I am expecting to also see the GDPR consent form.
For anyone wondering the same thing. The GDPR message was not appearing because I had not finalised my Admob account setup. I had not added my payment method. After adding it (and sending the app for review in Admob) the GDPR message started to appear.
I am developing an application that uses the Face/Touch ID at the opening.
I achieved this by adding this func to my MainViewController():
let context = LAContext()
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) {
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "Verifying") { (success, err) in
if success {
DispatchQueue.main.async {
self.loginSuccessfull()
self.button.removeFromSuperview()
}
} else {
if let err = err {
print(err)
}
}
}
}
This gets called both in the ViewDidLoad and by a button, as shown in this video.
As you can see tho, when I try to close my App it has a very weird behavior, and I am sure that it is caused by the FaceID.
Any suggestion to fix this?
Crash log:
Error Domain=com.apple.LocalAuthentication Code=-4 "Caller moved to background." UserInfo={NSLocalizedDescription=Caller moved to background.}
I believe I have found a solution for the issue, by delaying the evaluation.
I noticed that when I have any kind of delay in UI before evaluation (for example: animation that move the logo up before showing the face ID alert) the crash stops altogether.
So I did another test with delay like so:
override func viewDidAppear(_ animated: Bool) {
let context = LAContext()
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Biometric test") { success, error in
DispatchQueue.main.async {
if success {
doSome()
} else {
if let error = error { print(error) }
}
}
}
}
}
}
With that implementation I had zero crashes.
*Note: I also tried different delay times, from 0.1 to 2.0 seconds, all worked for me.
I've been stuck on a bug since last Monday, so I'm asking for help now ..
Contacts and Micriohpone request access does not work on iOS 9. I use this piece of code in order to request access to contacts :
let contactsStore = CNContactStore()
func requestAccess(completionHandler: #escaping (Permission) -> ()) {
self.contactsStore.requestAccess(for: .contacts, completionHandler: { (granted, error) in
if granted {
completionHandler(.granted)
} else {
completionHandler(.denied)
}
})
}
This function is called, no problem with that, the problem is it always return .denied and an error set with "Access denied", even though no alert has been shown to the user. The same with microphone.
The key 'Privacy - Contacts Usage Description' is present in my Info.plist
EDIT :
I also know that when the user denied once the usage it is not shown anymore, but the other problem is that there is not "switch" in the settings section of the app. I tried to restore the device (Working on simulator as I don't have a real iOS 9 device), but still the same behaviour.
This code works perfeclty on iOS 10 and iOS 11. But no chance on iOS 9
If you could help me on this issue that would be awesome.
Thanks !
I tried this on 9.3 in the simplest way imaginable, and I did get a prompt:
import UIKit
import Contacts
class ViewController: UIViewController {
let contactsStore = CNContactStore()
override func viewDidAppear(_ animated: Bool) {
DispatchQueue.main.async {
self.requestAccess(completionHandler: { (permission) in
print("The user said \(permission)")
})
}
}
func requestAccess(completionHandler: #escaping (Permission) -> ()) {
self.contactsStore.requestAccess(for: .contacts, completionHandler: { (granted, error) in
if granted {
completionHandler(.granted)
} else {
completionHandler(.denied)
}
})
}
}
enum Permission {
case granted
case denied
}
This works fine. I think the issue is that you already denied it.
The only solutions are:
Change the bundle id, which will make your app act as a different one
Reset your device/simulator (easier if a simulator of course)
Change the privacy setting from Off to On
For end users, I've seen the UI prompt the user to change the setting if they see "denied".
You can do that like this:
self.requestAccess(completionHandler: { (permission) in
print("The user said \(permission)")
if ( permission == .denied ) {
let urlStr = UIApplicationOpenSettingsURLString
if let url = URL(string:urlStr) {
UIApplication.shared.openURL(url)
}
}
})
In watchOS2 I am getting the sessionToken from the iOS app with WCSession. That works great but when I try to call become in background it never returns. The console logs 1 and 2 but never makes it to print("3"). Any reason becomeInBackground wouldn't respond at all? Or is there a completely different way I should be going about this?
The sessionToken is coming from user.sessionToken in iOS.
print("1")
if let sessionToken = reply["sessionToken"] as? String {
print("2")
PFUser.becomeInBackground(sessionToken, block: { (user, error) in
print("3")
if let user = user as? User where error == nil {
print("success")
} else {
print(error)
}
})
}