The documentation that Apple has provided for TouchID implementation for iOS 8 is in Objective-C.
Is there a Swift version of it?
Objective-C:
- (IBAction)touchIDAvailable:(UIButton *)touchIDAvailableButton {
LAContext *context = [[LAContext alloc] init];
__block NSString *msg;
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:NSLocalizedString(#"Place your finger on the sensor", nil) reply: ^(BOOL success, NSError *authenticationError) {
if (success) {
}
}
}
Swift:
#IBAction func touchidbutton(sender: AnyObject) {
authContext.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: "Place your finger on the sensor"?, reply: ((success : Bool, NSError!) -> Void)?){
if (success) {
}
}
}
Here is my view controller that does these checks in Swift. While working on this I found the completion block/closure syntax in Swift to be very confusing.
Notice that some of the options changed in Beta 2 in order to give you more control over the Touch ID dialog such as disabling the fallback option or the cancel button.
// Imports
import UIKit
import LocalAuthentication
// Class Implementation
class FirstViewController: UIViewController {
// Class Properties
#IBOutlet var statusLabel : UILabel
#IBOutlet var headerString: UILabel
var authError : NSError?
var authContext = LAContext()
var statusText = ""
var alert = UIAlertController(title: "", message: "", preferredStyle: UIAlertControllerStyle.Alert)
// Class Methods
#IBAction func swiftButtonPress(sender : AnyObject) {
statusLabel.text = "Authenticating"
//Can we use local auth?
if authContext.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &authError) {
authContext.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics,
localizedReason: "I need to see if it's really you",
reply: {(success: Bool, error: NSError!) -> Void in
if success {
self.statusText = "Touch ID success!"
self.alert.title = "Success"
self.alert.message = "I knew it was you!"
} else {
self.statusText = "Touch ID failed!"
self.alert.title = "Failure"
switch error!.code {
case LAError.AuthenticationFailed.toRaw():
self.alert.message = "Authentication Failed"
case LAError.UserCancel.toRaw():
self.alert.message = "User canceled!"
case LAError.SystemCancel.toRaw():
self.alert.message = "The system canceled!"
case LAError.UserFallback.toRaw():
self.alert.message = "User request to enter passcode"
default:
self.alert.message = "Something else went wrong"
}
}
self.presentViewController(self.alert, animated: true, completion:{self.statusLabel.text = self.statusText})
})
} else {
self.statusText = "No local authentication"
alert.title = "Uh oh!"
switch authError!.code {
case LAError.TouchIDNotAvailable.toRaw():
alert.message = "No Touch ID on device"
case LAError.TouchIDNotEnrolled.toRaw():
alert.message = "No fingers enrolled"
case LAError.PasscodeNotSet.toRaw():
alert.message = "No passcode set"
default:
alert.message = "Something went wrong getting local auth"
}
self.presentViewController(self.alert, animated: true, completion: {self.statusLabel.text = self.statusText})
}
resetTouchID()
}
// Reset the system so we can go again
func resetTouchID() {
authContext = LAContext()
alert = UIAlertController(title: "", message: "", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil))
let passcodeDetector = SwiftPasscodeDetector()
if passcodeDetector.checkForPasscode() {
headerString.text = "Passcode Set on Device"
} else {
headerString.text = "No Passcode Set"
}
}
// Inherited Methods
override func viewDidLoad() {
super.viewDidLoad()
resetTouchID()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The API name is LAContext, and it's in the docs right here. It's pretty sparse, but it does its job. The method you probably want is
evaluatePolicy(_ policy: LAPolicy,
localizedReason localizedReason: String!,
reply reply: ((Bool, NSError!) -> Void)!)
The string argument is a subheader to display to the user, the reply is simply a callback block, and the policy currently has to be LAPolicy.DeviceOwnerAuthenticationWithBiometrics, but it appears the framework is there for other types of authentication in the future. Interesting...
Hope that helps! I tried on my phone because I was curious and it works wonderfully. Just make sure to query for ability to evaluate policy before you try to use it, in case it's on an older device.
The LAContext reference has method signatures in both Obj-C and Swift. Furthermore, if you ⌘-click on the LAContext class in your Swift code, you should be able to view the generated "header" for it in Swift.
Updated to Swift 3
static func authorizeWithTouchIDIfPossible(){
let authContext = LAContext()
var authError : NSError?
if authContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
authContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "I need to see this", reply: { (success, error) in
if success {
print("Touch ID success!")
DispatchQueue.main.async {
//Do stuff here
}
} else {
print("Touch ID failed!")
}}
);
} else {
print("No local authentication")
}
}
Found it!
The link below is from a user named Shmoopi from Github. Shmoopi made the app to test the TouchID programmed in Swift.
https://github.com/Shmoopi/Swift-Touch-ID
Swift 3.0 in:
import UIKit
import LocalAuthentication
class ViewController: UIViewController
{
#IBAction func TouchBtn(_ sender: AnyObject)
{
let context:LAContext = LAContext()
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error:nil)
{
context.evaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, localizedReason:"We Need Your Id", reply:{
(wasSuccessful,Error) in
if wasSuccessful
{
print("Was a Sucess")
}
else
{
print("Not Logged In")
}
})
}
}
}
Swift 5 :
import LocalAuthentication
func authenticateUser() {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
let reason = "Identify yourself!"
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) {
[unowned self] success, authenticationError in
DispatchQueue.main.async {
if success {
print("Success!")
} else {
let ac = UIAlertController(title: "Authentication failed", message: "Sorry!", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self.present(ac, animated: true)
}
}
}
} else {
let ac = UIAlertController(title: "Touch ID not available", message: "Your device is not configured for Touch ID.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
present(ac, animated: true)
}
}
Related
I am using Face id in my iOS app. Be default when user fails to authenticate try again not working. I want to add callback when user click on try again.Please see my below code.I have searched a lot but nothing found about face id.
func startFaceIDTest(){
attempted = true
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
let reason = "Identify yourself!"
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) {
[unowned self] (successfaceID, authenticationError) in
DispatchQueue.main.async {
if successfaceID {
self.success = true
//self.unlockSecretMessage()
} else {
self.success = false
// let ac = UIAlertController(title: "Authentication failed", message: "You could not be verified; please try again.", preferredStyle: .alert)
// ac.addAction(UIAlertAction(title: "OK", style: .default))
// self.present(ac, animated: true)
}
}
}
} else {
if let err = error {
if #available(iOS 11.0, *) {
self.success = false
switch err.code {
case LAError.Code.biometryNotEnrolled.rawValue:
notifyUser("Your device not enrolled for biometric",
err: err.localizedDescription)
case LAError.Code.passcodeNotSet.rawValue:
notifyUser("A passcode has not been set",
err: err.localizedDescription)
case LAError.Code.biometryNotAvailable.rawValue:
notifyUser("Biometric authentication not available",
err: err.localizedDescription)
default:
notifyUser("Unknown error",
err: err.localizedDescription)
}
} else {
// Fallback on earlier versions
}
}
}
}
I've created an appointment reminder section in my app but it seems the first use of the app doesn't get stored? When I click the create reminder button I get my popup alert saying it was successfully created followed by the would like to access your reminders popup. Because of this people's first appointments aren't being stored, and I can't figure out what I have done wrong?
Here is my code:
import UIKit
import EventKit
class FirstViewController: UIViewController {
#IBOutlet weak var reminderText: UITextField!
#IBOutlet weak var myDatePicker: UIDatePicker!
let appDelegate = UIApplication.shared.delegate
as! AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func setReminder(_ sender: AnyObject) {
if reminderText.text == "" {
// Create the alert controller
let alertController = UIAlertController(title: "Information Needed", message: "Please type in your treatment and select the correct date and time you wish to be reminded about before pressing the Create Appointment Reminder button.", preferredStyle: .alert)
// Create the actions
let okAction = UIAlertAction(title: "Got It", style: UIAlertActionStyle.default) {
UIAlertAction in
NSLog("OK Pressed")
}
// Add the actions
alertController.addAction(okAction)
// Present the controller
self.present(alertController, animated: true, completion: nil)
} else {
if appDelegate.eventStore == nil {
appDelegate.eventStore = EKEventStore()
appDelegate.eventStore?.requestAccess(
to: EKEntityType.reminder, completion: {(granted, error) in
if !granted {
print("Access to store not granted")
print(error!.localizedDescription)
} else {
print("Access granted")
}
})
}
if (appDelegate.eventStore != nil) {
self.createReminder()
}
}
self.reminderText.resignFirstResponder()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func createReminder() {
let reminder = EKReminder(eventStore: appDelegate.eventStore!)
reminder.title = reminderText.text! + " " + "(Pose Beauty Salon)"
reminder.calendar =
appDelegate.eventStore!.defaultCalendarForNewReminders()
let date = myDatePicker.date
let alarm = EKAlarm(absoluteDate: date)
reminder.addAlarm(alarm)
do {
try appDelegate.eventStore?.save(reminder,
commit: true)
} catch let error {
print("Reminder failed with error \(error.localizedDescription)")
}
// Create the alert controller
let alertController = UIAlertController(title: "Reminder Created Successfully", message: "Your \(reminderText.text!) appointment reminder at Pose Beauty Salon has been successfully created in your iPhone Reminders app. Thank You! ", preferredStyle: .alert)
// Create the actions
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
UIAlertAction in
NSLog("OK Pressed")
}
// Add the actions
alertController.addAction(okAction)
// Present the controller
self.present(alertController, animated: true, completion: nil)
reminderText.text = ""
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
reminderText.endEditing(true)
}
}
Your logic is much more complicated than it needs to be. You can take advantage of the fact that when you request access to the event store after access has already been granted (or denied) then those permissions are used without prompting the user.
It is also poor form to initialise a property of the AppDelegate from some other class. You probably don't even need the EventStore on the AppDelegate - you can just create an instance when you need it.
You can use something like:
} else {
let eventStore = EKEventStore()
eventStore.requestAccess(
to: EKEntityType.reminder, completion: {(granted, error) in
if !granted {
print("Access to store not granted")
print(error!.localizedDescription)
} else {
print("Access granted")
self.createReminder(in: eventStore)
}
})
}
func createReminder(in eventStore: EKEventStore) {
....
}
I have a login page in my app. The user enters their username and their password. I have an API that tells me if the username and password are correct and the user's id if they are. If they are not correct it shows a UIAlertView() that asks if you would like to create an account. The view has two buttons. A "No" button which dismisses the view and a "Yes" button which is supposed to contact an API to create the user's account. I have created alert actions before but it will not work with the code I have below. If you wouldn't mind could you please take a look and help me diagnose the problem?
//
// File.swift
// Reading Logs
//
// Created by Andrew on 12/8/15.
// Copyright © 2015 Wilson Apps for Education. All rights reserved.
//
import UIKit
class UserLogin {
var loginAlert = UIAlertView()
var user: String = ""
var pass: String = ""
func checkLogin() -> Bool{
let defaults = NSUserDefaults.standardUserDefaults()
let stat = defaults.valueForKey("loggedIn")
if(String(stat!) == "0"){
return false
}else{
return true
}
}
func logout(){
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue("0", forKey: "loggedIn")
defaults.setValue("", forKey: "logKey")
defaults.setValue("0", forKey: "userKey")
}
func login(username username: String, password: String, completion: (result: String) -> Void){
self.user = username
self.pass = password
let url = "http://www.wilsonfamily5.org/rlog/checkLogin.php?u=\(username)&p=\(password)"
let nsUrl = NSURL(string:url)
let nsUrlRequest = NSURLRequest(URL: nsUrl!)
NSURLSession.sharedSession().dataTaskWithRequest(nsUrlRequest){
(data, response, error) in
guard
let data = data,
let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
else { return }
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if(contents as String == "0"){
self.loginAlert = UIAlertView(title: "No Account Found", message: "We did not find an account matching that criterea. Do you want us to create you an account?", delegate:nil, cancelButtonTitle: "No")
self.loginAlert.addButtonWithTitle("Yes")
self.loginAlert.show()
}else{
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue(contents as String, forKey: "userKey")
defaults.setValue("1", forKey: "loggedIn")
completion(result: "1")
}
})
}.resume()
}
func register(username: String, password: String){
let url = "http://www.wilsonfamily5.org/rlog/newAccount.php?u=\(username)&p=\(password)"
let nsUrl = NSURL(string:url)
let nsUrlRequest = NSURLRequest(URL: nsUrl!)
NSURLSession.sharedSession().dataTaskWithRequest(nsUrlRequest){
(data, response, error) in
guard
let data = data,
let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
else { return }
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue(contents as String, forKey: "userKey")
defaults.setValue("1", forKey: "loggedIn")
})
}.resume()
}
func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
print("ButtonClicked")
if(buttonIndex == 1){
print("1ButtonClicked")
register(user, password: pass)
}
}
}
Step-1
Add UIAlertViewDelegate to your class;
class UserLogin, UIAlertViewDelegate {
....
Step-2
Set delegate and implement "Yes" button loginAlert object;
self.loginAlert = UIAlertView(title: "No Account Found", message: "We did not find an account matching that criterea. Do you want us to create you an account?", delegate: self, cancelButtonTitle: "No", otherButtonTitles: "Yes")
self.loginAlert.show()
Now clickedButtonAtIndex function will be triggered.
You should use UIAlertViewController instead of UIAlertView because
UIAlertView is deprecated in iOS 9
Here is a code of UIAlertController in Swift and its pretty simple to use.The main thing is that it's Block based and No need to use any delegate
let alertController = UIAlertController(title: "Default AlertController", message: "A standard alert", preferredStyle: .Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action:UIAlertAction!) in
println("you have pressed the Cancel button");
}
alertController.addAction(cancelAction)
let OKAction = UIAlertAction(title: "OK", style: .Default) { (action:UIAlertAction!) in
println("you have pressed OK button");
}
alertController.addAction(OKAction)
self.presentViewController(alertController, animated: true, completion:nil)
You need to set Delegate, so you can receive alerts callbacks:
self.loginAlert = UIAlertView(title: "No Account Found", message: "We did not find an account matching that criterea. Do you want us to create you an account?", delegate:self, cancelButtonTitle: "No"
Ultimately, what I want to have is one function (or probably a function within a separate class) that prompts the user to authenticate via TouchID, then passcode and if either of these are successful then returns a true boolean.
I've figured out the authentication mostly however I can't get the function to return a boolean, here's roughly what I have so far:
The authenticate user function:
func authenticateUser() -> Bool {
let context = LAContext()
var error: NSError?
let reasonString = "Authentication is needed to access your places."
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error) {
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: { (success, policyError) -> Void in
if success {
print("touchID authentication succesful")
} else {
switch policyError!.code {
case LAError.UserFallback.rawValue:
print("User selected to enter password.")
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.showPasswordAlert()
})
default:
print("Authentication failed! :(")
}
}
})
} else {
print(error?.localizedDescription)
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.showPasswordAlert()
})
}
return true
}
It's just set to return true for now for testing purposes. However I'd like to have it return true whenever there's a successful authentication. I can't place the return within the context.evaluatePolicy because it's inside the block method. Is there another way to do what I want? Or am I going about this in totally the wrong manner?
Also, for reference here is my showPasswordAlert function:
func showPasswordAlert() {
let alertController = UIAlertController(title: "Passcode", message: "Please enter your passcode.", preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Default) { (action) -> Void in
if let textField = alertController.textFields?.first as UITextField? {
if let passcode = self.keychainWrapper.myObjectForKey("v_Data") as? String {
if textField.text == passcode {
print("Authentication successful! :) ")
} else {
}
}
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil)
alertController.addAction(defaultAction)
alertController.addAction(cancelAction)
alertController.addTextFieldWithConfigurationHandler { (textField) -> Void in
textField.placeholder = "Enter passcode..."
textField.textAlignment = NSTextAlignment.Center
textField.secureTextEntry = true
textField.keyboardType = UIKeyboardType.NumberPad
}
self.presentViewController(alertController, animated: true, completion: nil)
}
So in my head what I'm thinking is: showPasswordAlert could also return a true boolean to authenticateUser and then this would in turn return a true boolean to where the authenticateUser function is being called. I know there's a simpler way to do that but I'd just like to get it working for now.
So after much trial and error I've come up with possibly what is the best solution for me at the moment.
It seems that since evaluatePolicy and co. are run asynchronously you can't return variables from them or access variables. You can however, call selectors from inside these blocks (why this is I have no idea).
So my current solution as of writing this post is to call the authenticate function as such:
func authenticateUserWithAction(actionSelector: Selector, object: AnyObject){}
I pass it an action (declared elsewhere in the class, but basically what you want to do if authentication is successful) and an object. The object is just incase the action requires something to be passed to the function. So in my app for example, after authentication a viewController is presented and an object on that viewController is set to an object in the original viewController. This object is passed in the authenticate function.
From within the authenticate user function I can call to an authenticateUserWithPasscode(actionSelector: Selector, object: AnyObject) that takes in the same action and object as the original authenticate function.
The action and object are passed down the chain until the user is authenticated and they are performed.
Pretty hacky code overall but it seems to be working fine for me.
Also had this problem, I ended up making a struct called Authentication which has a static function authenticate which gets passed the view controller from where you're calling it and a callback function:
import LocalAuthentication
import UIKit
struct Authentication {
static func authenticate(viewController: UIViewController, callback:
#escaping (Bool) -> ()) {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
let reason = "Please Authenticate"
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) {
[weak viewController] success, authenticationError in
guard let viewController = viewController else {
return
}
DispatchQueue.main.async {
if success {
callback(true)
} else {
let ac = UIAlertController(title: "Authentication failed", message: "Please try again", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
viewController.present(ac, animated: true)
callback(false)
}
}
}
} else {
let ac = UIAlertController(title: "Touch ID not available", message: "Your device is not configured for Touch ID.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
viewController.present(ac, animated: true)
callback(false)
}
}
}
Then calling it:
Authentication.authenticate(viewController: parentViewController, callback: {
[weak self] (authenticated: Bool) in
if authenticated {
self?.yourFunctionHere()
}
})
I'm looking to create certain events when certain error codes occur in my program. For instance, if there is an error code 200, I need to let the user know they are missing the username field. Or for an error code 125, I need to let them know they did not enter a valid email address when creating for an account. How do I target these error codes specifically? I have tried the code below with no success, what am I doing wrong and is this possible?
if error.code == 125 {
var invalidEmail:UIAlertView = UIAlertView(title: Please try again, message: "That does not look like a real email address. Please enter a real one.", delegate: self, cancelButtonTitle: "Try again")
invalidEmail.show()
}
The error that xCode is telling me is that I have an unresolved identifier 'error'. There is an instance of this in the parse starter project file 'AppDelegate.swift' that calls the following code and it seems to work perfectly fine.
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
if error.code == 3010 {
println("Push notifications are not supported in the iOS Simulator.")
} else {
println("application:didFailToRegisterForRemoteNotificationsWithError: %#", error)
}
}
My code
#IBAction func signupTapped(sender: AnyObject) {
let fullname = fullnameField.text
let email = emailField.text
let username = usernameField.text
let password = passwordField.text
var user = PFUser()
user.username = username
user.password = password
user.email = email
// other fields can be set just like with PFObject
user["fullname"] = fullname
if error.code == 125 {
let alert = UIAlertController(title: "Please try again", message: "That does not look like a valid email address.", preferedStyle: .Alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
presentViewController(alert, animated: true)
}
if fullname.isEmpty || email.isEmpty || username.isEmpty || password.isEmpty {
let emptyFieldsError:UIAlertView = UIAlertView(title: "Please try again", message: "Please fill out all the fields so that we can create your account.", delegate: self, cancelButtonTitle: "Try again")
emptyFieldsError.show()
}
user.signUpInBackgroundWithBlock {
(succeeded: Bool, error: NSError?) -> Void in
if let error = error {
let errorString = error.userInfo?["error"] as? NSString
// Show the errorString somewhere and let the user try again.
} else {
// Hooray! Let them use the app now.
}
}
}
There is no error defined where you have your code. You can only reference error from inside a scope in which it exists.
user.signUpInBackgroundWithBlock {
(succeeded: Bool, error: NSError?) -> Void in
if let error = error {
let errorString = error.userInfo?["error"] as? NSString
// PUT YOUR ERROR HANDLING CODE HERE
} else {
}
}
As #Larme already pointed out, UIAlertView is deprecated, so you should use UIAlertController:
if error.code == 125 {
let alert = UIAlertController(title: "Please try again", message: "That does not look like a valid email address.", preferedStyle: .Alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
presentViewController(alert, animated: true)
}