i have used LocalAuthentication in my app for Touch ID authentication but Touch ID alert display in every UIViewController for authentication, so how to call only once in a UIViewController?
You just create a BOOL variable and set the value is FALSE in appDelegate and when authenticate in one viewController, update the value of the BOOL variable to TRUE. In every viewController check the BOOL variable wheather it is TRUE or FALSE and code accordingly.
Use this simple API which've written to ease your usage:
import UIKit
import LocalAuthentication
typealias completionHandler = (evaluationComplete: Bool) -> ()
func showTouchID(Reason reason: String, _ shouldShow: Bool, SuperDone : completionHandler){
guard shouldShow == true else{
return
}
let bike = LAContext()
guard bike.canEvaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, error: nil) else{
SuperDone(evaluationComplete: false)
return
bike.evaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reason) {(_, error) in
dispatch_async(dispatch_get_main_queue(), {
guard error != nil else{
SuperDone(evaluationComplete: false)
return
}
SuperDone(evaluationComplete: true)
})
}
}
Use it wherever you want to like:
showTouchID(Reason: "Are", true){ success in
if success{
}
else{
}
}
Related
In my app I want to ask for camera access when the user pressed a camera button, which would take him to an AVFoundation based camera live preview.
That view is presented using a "present modally segue".
So in my ViewController I currently override the shouldPerformSegue() and return true if the user gives permission or has granted it already, otherwise false.
If the user didn't grant access I am showing an Alert in which he can go to settings to change the permission. That is done in showPermissionInfo().
My problem is, that AVCaptureDevice.requestAccess is called asynchronously and thus hasCameraPermission is not set to true before I'm checking for it.
Is there a way to call these restriction accesses in a blocking way?
Thank you!
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "Modally_ToCameraViewController"
{
var hasCameraPermission = false
if AVCaptureDevice.authorizationStatus(for: .video) == .authorized
{
hasCameraPermission = true
} else {
AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
if granted {
hasCameraPermission = true
} else {
hasCameraPermission = false
}
})
}
if(!hasCameraPermission){
showPermissionInfo()
}
return hasCameraPermission
}
return true
}
One easy solution would be to create a semaphore and wait on it until the completion closure is called. semaphore.wait will block the current thread until semaphore.signal is called.
let semaphore = DispatchSemaphore(value: 0)
AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
if granted {
hasCameraPermission = true
} else {
hasCameraPermission = false
}
semaphore.signal()
})
semaphore.wait()
In objC the syntax written by Rawendrich for GKTurnBasedEventListener, which was GKTurnBasedEventHandler there at that time, now changed by Apple is as below.
if (!gameCenterAvailable) return;
void (^setGKEventHandlerDelegate)(NSError *) = ^ (NSError *error)
{
GKTurnBasedEventHandler *ev =
[GKTurnBasedEventHandler sharedTurnBasedEventHandler];
ev.delegate = self;
};
NSLog(#"Authenticating local user...");
if ([GKLocalPlayer localPlayer].authenticated == NO) {
[[GKLocalPlayer localPlayer]
authenticateWithCompletionHandler:
setGKEventHandlerDelegate];
} else {
NSLog(#"Already authenticated!");
setGKEventHandlerDelegate(nil);
}
Now after converting this to swift, and with the composition of writing down GKTurnBasedEventListener instead of GKTurnBasedEventHandler, this comes the following way.
// Converted with Swiftify v1.0.6381 - https://objectivec2swift.com/
if !gameCenterAvailable {
return
}
var setGKEventHandlerDelegate: ((_: Error) -> Void)? = {(_ error: Error?) -> Void in
var ev = GKTurnBasedEventHandler.shared()
ev.delegate = self
}
print("Authenticating local user...")
if GKLocalPlayer.localPlayer().authenticated == false {
GKLocalPlayer.localPlayer().authenticate(withCompletionHandler: setGKEventHandlerDelegate)
}
else {
print("Already authenticated!")
setGKEventHandlerDelegate(nil)
}
Unfortunately this is not the right syntax to set delegate of GKTurnBasedEventListener for my ViewController.
Please if anyone of you could solve this for me, because without this I'm not able to read through event listener's default functions.
Cheers!
FYI, if you want a working example of how to use GKLocalPlayerListener during a turn-based GameKit match, you are welcome to take a look at this example project for a turn based game. I hope it helps to see all the pieces in context.
Finally after just about harsh 10 hours, I figured out this problem from Here. Although this syntax is in objC, but there's no problem of converting it to swift from Swiftify.
Although a bit later than the real time, but I'm now able to understand that setting delegate of GKTunBasedEventListener is not like the one we do for UITableViewControllerDelegate.
Here, one must have to first authenticate local player, then after you have to register local player's listener to the ViewController's delegate GKLocalPlayerListener.
One other thing I found on Apple's Documentation:
Do not implement GKChallengeListener, GKInviteEventListener, GKSavedGameListener, and GKTurnBasedEventListener directly; implement GKLocalPlayerListener instead. You can listen for and handle multiple events using GKLocalPlayerListener.
So then on I've implemented in the following way.
import GameKit
class ViewController: UIViewController, GKTurnBasedMatchmakerViewControllerDelegate,
GKLocalPlayerListener {
.....
func player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch, didBecomeActive: Bool) {
print("#1")
print(player)
print("#2")
print(match)
print("#3")
print(didBecomeActive)
if match.status == GKTurnBasedMatchStatus.open
{
if GKLocalPlayer.localPlayer() == match.currentParticipant
{
if didBecomeActive
{
// Active now
}
else
{
// Active already
}
}
else
{
// It's someone's turn
if match.matchData != myMatch?.matchData
{
// Match Data being Updated by Someone
print(player.alias ?? "No Name:")
}
}
}
thirdTopLabel.text = match.matchID! + "\n" + didBecomeActive.description
}
....
Now in ViewDidLoad() function put the following code.
// In the ViewDidLoad function
if(!GKLocalPlayer.localPlayer().isAuthenticated)
{
authenticatePlayer { (auth) in
weak var weakSelf = self
weak var weakPlayer = GKLocalPlayer.localPlayer()
if(auth){
weakPlayer?.register(weakSelf!)
self.suthentication = true;
}
else{
print("failed in authentication")
self.suthentication = false;
}
}
}
else
{
// Already Authenticated
GKLocalPlayer.localPlayer().register(self)
localPlayer = GKLocalPlayer.localPlayer()
}
And finally your Authentication function should be like this.
// authenticate local player :: Just Authentication
func authenticatePlayer(completionHandler: #escaping (_ resultedPlaces: Bool) -> Void) {
localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler =
{ (viewController , error ) -> Void in
if viewController != nil
{
self.present(viewController!, animated:true, completion: nil)
}
else
{
if self.localPlayer.isAuthenticated
{
completionHandler(true);
}
else
{
completionHandler(false);
print("not able to authenticate fail")
self.gameCenterEnabled = false
if (error != nil)
{
print("\(error.debugDescription)")
}
else
{
print( "error is nil")
}
}
}
}
}
NOTE: GKLocalPlayerListener won't work on simulator.
I have a Settingsviewcontroller.swift file that is connected to the storyboard and a separate file called connectionapi.swift.
Now, when a user clicks the checkbutton, the given username and password are checked against the API in the connectionapi class. Now when for instant the result generates an error I want to inform the user with an alert message. I am trying to find out for a few days how to do this, but I can't seem to find it. I keep ending up in errors.
Can someone please help me with some code examples:
the viewcontroler file
//
// ViewControllerSettings.swift
//
import Foundation
import UIKit
class ViewControllerSettings: UIViewController {
#IBOutlet weak var tGebruikersnaam: UITextField!
#IBOutlet weak var tCode: UITextField!
#IBOutlet weak var AnimatedImage: UIImageView!
#IBOutlet weak var lCopyright: UILabel!
#IBOutlet weak var lStatus: UILabel!
// declare a iCloud Store to save and load data from
var iCloudStore:NSUbiquitousKeyValueStore!
var userName:String = ""
var password:String = ""
let API = myAPI()
#IBAction func didTapOpslaan(_ sender: Any) {
if tGebruikersnaam.text == "" || tGebruikersnaam.text == nil || tCode.text == "" || tCode.text == nil {
return
}
print("Lets check username and password against the api")
let newUsername = "\(tGebruikersnaam.text!)"
let newCode = "\(tCode.text!)"
API.CheckUsernamePassword(username: newUsername, code: newCode) {
isValid in
print(isValid)
if isValid == true {
DispatchQueue.main.async {
print("The credentials are correct")
self.ShowAnimationOk()
}
}else {
DispatchQueue.main.async {
print("the credentials are wrong")
self.ShowAnimationNo()
}
}
}
}
// Code removed
}
The API Swift file
//
// ConnectAPI.swift
import Foundation
import UIKit
class myAPI{
let api_key = "b88a734f186sad"
let baseurl = "https://xxxx:443/xxxx/api"
func CheckUsernamePassword(username :String ,code:String, completion: #escaping (Bool)->() ) {
let urlString = "\(self.baseurl)/accounts/validateusernamepassword.json?username=\(username)&password=\(code)&api_key=\(self.api_key)"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print("API | Error URLSession : \(error!)")
completion(false)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: []) as! [String:Any]
print("----------")
if parsedData["validated"] != nil {
if "\(parsedData["validated"]!)" == "1" {
print("API JSON | validated = \(parsedData["validated"]!)")
print("API JSON | message = \(parsedData["message"]!)")
completion(true)
}else {
print("Credential Check not valid")
print("API JSON | validated = \(parsedData["validated"]!)")
print("API JSON | message = \(parsedData["message"]!)")
completion(false)
}
}else{
print("Json Parse error: \(parsedData)")
// Raise a Alert here
}
} catch let error as NSError {
print("API | Error Parsing JSON \(error)" )
// Raise a Alert here
// main.showAlert(message: "API | Error Parsing JSON \(error)")
//A error occured when checking credentials, try again later.
completion(false)
}
}
}.resume()
}
I'm not sure what's your problem is. I assume that you have two problems:
you don't know how to pass error message from your API class to
the ViewControllerSettings class.
you don't know how to show alert in iOS before
If that's the case, let's look at this.
1. Passing error message
You can see that the completion block (e.g., #escaping (Bool) -> ()) will be called after the function checkUsernamePassword done with the URLSession's dataTask:
func CheckUsernamePassword(username :String ,code:String, completion: #escaping (Bool)->() ) { ... }
If success, you call the block with value true
completion(true)
in other cases you call with value false
completion(false)
To pass more informations (e.g. error message), you can simply add input parameter at the completion block. Better added names for each one so it's more clear. E.g., change it to:
..., completion: #escaping (valid: Bool, errorMessage: String?) -> ())
Then, if the API succeeds, you call it without errorMessage:
completion(valid: true, errorMessage: nil)
And if it's error, you pass either your own error message or get it from NSError
completion(valid: false, errorMessage: "Credential check not valid.")
2. Show the error in UIAlertController
First make a utility function that shows standard alert in your ViewControllerSettings:
// Simple Alert UI
func showAlert(title: String, message: String) {
let actionSheetController: UIAlertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
// add close button
let cancelAction: UIAlertAction = UIAlertAction(title: "Close", style: .cancel) { _ in }
actionSheetController.addAction(cancelAction)
// show on self
self.present(actionSheetController, animated: true, completion: nil)
}
If you did the first step, you have accessed to your error message at call site (ViewController).
When you call the API, errorMessage is available, just show it with alert:
// didTap...
API.CheckUsernamePassword(username: newUsername, code: newCode) {
(isValid, errorMessage) in
if isValid {
...
} else {
let error = errorMessage! // I don't recommend forced-casting though
self.showAlert(title: "Error!", message: error) // show Alert UI
}
}
func checkIfFriend()->Bool{
request(.POST, "", parameters:["":""]).responseJSON{_,_,jsonData in
if something{
return true}
else{
return false
}
}
It appears that "return true/false" has to be in the same level than function is and not inside another function (in this case the Alamofire one).
In that case, how can I return bool in checkIfFriend function depending on what the request return?
Your checkIfFriend() function does not run on the same thread as your Alamofire request (Which runs asynchronously). You should use a callback function/ completion handler as shown below:
func checkIfFriend(completion : (Bool, Any?, Error?) -> Void) {
request(.POST, "", parameters:["":""]).responseJSON{_,_,jsonData in
if something{
completion(true, contentFromResponse, nil)
//return true
}else{
completion(false, contentFromResponse, nil)
// return false
}
}
//Then you can call your checkIfFriend Function like shown below and make use
// of the "returned" bool values from the completion Handler
override func viewDidLoad() {
super.viewDidLoad()
var areWeFriends: Bool = Bool()
var responseContent: Any = Any()
checkIfFriend(completion: { (success, content, error) in
areWeFriends = success // are We Friends will equal true or false depending on your response from alamofire.
//You can also use the content of the response any errors if you wish.
})
}
I'm implementing the login possibility with touchID using Swift.
Following: when the App is started, there is a login screen and a touchID popup - that's working fine. The problem occurs, when the app is loaded from background: I want the touchID popup appear over a login screen if a specific timespan hasn't been exceeded yet - but this time I want the touchID to go to the last shown view before the app entered background. (i.e. if the user wants to cancel the touchID, there is a login screen underneath where he then can authenticate via password, which leads him to the last shown view OR if the touchID authentication succeeded, the login screen should be dismissed and the last shown view presented.)
I really tried everything on my own, and searched for answers - nothing did help me. Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
//notify when foreground or background have been entered -> in that case there are two methods that will be invoked: willEnterForeground and didEnterBackground
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "willEnterForeground", name:UIApplicationWillEnterForegroundNotification, object: nil)
notificationCenter.addObserver(self, selector: "didEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
password.secureTextEntry = true
if (username != nil) {
username.text = "bucketFit"
}
username.delegate = self
password.delegate = self
if let alreadyShown : AnyObject? = def.objectForKey("alreadyShown") {
if (alreadyShown == nil){
authenticateWithTouchID()
}
}
}
willEnterForeground:
func willEnterForeground() {
//save locally that the guide already logged in once and the application is just entering foreground
//the variable alreadyShown is used for presenting the touchID, see viewDidAppear method
def.setObject(true, forKey: "alreadyShown")
if let backgroundEntered : AnyObject? = def.objectForKey("backgroundEntered") {
let startTime = backgroundEntered as! NSDate
//number of seconds the app was in the background
let inactivityDuration = NSDate().timeIntervalSinceDate(startTime)
//if the app was longer than 3 minutes inactiv, ask the guide to input his password
if (inactivityDuration > 2) {
showLoginView()
} else {
def.removeObjectForKey("alreadyShown")
showLoginView()
}
}
}
authenticateWithTouchID():
func authenticateWithTouchID() {
let context : LAContext = LAContext()
context.localizedFallbackTitle = ""
var error : NSError?
let myLocalizedReasonString : NSString = "Authentication is required"
//check whether the iphone has the touchID possibility at all
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error) {
//if yes then execute the touchID and see whether the finger print matches
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString as String, reply: { (success : Bool, evaluationError : NSError?) -> Void in
//touchID succeded -> go to students list page
if success {
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.performSegueWithIdentifier("studentsList", sender: self)
})
} else {
// Authentification failed
print(evaluationError?.description)
//print out the specific error
switch evaluationError!.code {
case LAError.SystemCancel.rawValue:
print("Authentication cancelled by the system")
case LAError.UserCancel.rawValue:
print("Authentication cancelled by the user")
default:
print("Authentication failed")
}
}
})
}
}
shouldPerformSegueWithIdentifier:
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if (false) { //TODO -> username.text!.isEmpty || password.text!.isEmpty
notify("Login failed", message: "Please enter your username and password to proceed")
return false
} else if (false) { //TODO when backend ready! -> !login("bucketFit", password: "test")
notify("Incorrect username or password", message: "Please try again")
return false
//if the login page is loaded after background, dont proceed (then we need to present the last presented view on the stack before the app leaved to background)
} else if let alreadyShown : AnyObject? = def.objectForKey("alreadyShown") {
if (alreadyShown != nil){
//TODO check whether login data is correct
dismissLoginView()
return false
}
}
return true
}
Thank you in advance.
What you could do is create a AuthenticationManager. This manager would be a shared instance which keep track of whether authentication needs to be renewed. You may also want this to contain all of the auth methods.
class AuthenticationManager {
static let sharedInstance = AuthenticationManager()
var needsAuthentication = false
}
In AppDelegate:
func willEnterForeground() {
def.setObject(true, forKey: "alreadyShown")
if let backgroundEntered : AnyObject? = def.objectForKey("backgroundEntered") {
let startTime = backgroundEntered as! NSDate
//number of seconds the app was in the background
let inactivityDuration = NSDate().timeIntervalSinceDate(startTime)
//if the app was longer than 3 minutes inactiv, ask the guide to input his password
if (inactivityDuration > 2) {
AuthenticationManager.sharedInstance.needsAuthentication = true
}
}
}
Then, subclass UIViewController with a view controller named SecureViewController. Override viewDidLoad() in this subclass
override fun viewDidLoad() {
super.viewDidLoad()
if (AuthenticationManager.sharedInstance().needsAuthentication) {
// call authentication methods
}
}
Now, make all your View Controllers that require authentication subclasses of SecureViewController.