Lee Andrew Lee Andrew - 6 months ago 22
Swift Question

iOS code executed out of order

I'm trying to change the text on a label depending on the result of a TouchID attempt of login, however there's a delay going on. The same thing happens when I tried to navigate to another view like my video shows here https://www.youtube.com/watch?v=gQI-93u_B1A

Suppose I have a switch to deal with the different login error possibilities, in each case, if I try to change the label there's this delay, if I use the cases to change the content of a string variable and try to assign the content of this variable to a label in the end of the function, a breakpoint shows that the line assigning the variable to the label gets executed before the block evaluating the login attempt, this would be the code:

func requestUserAuthentication() {

var myContext:LAContext = LAContext()
var authError:NSError?
var myLocalizedRasonMessage = "Please authenticate using your fingerprint"

if (myContext.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error:&authError)) {
myContext.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedRasonMessage) { success, error in

if (success) {
self.requestAuthenticationOutput = "Login successful"
}

else {
switch error.code {
case LAError.AuthenticationFailed.toRaw():
self.requestAuthenticationOutput = "Login failed"

case LAError.UserCancel.toRaw():
self.requestAuthenticationOutput = "User canceled"

case LAError.SystemCancel.toRaw():
self.requestAuthenticationOutput = "System canceled"

case LAError.UserFallback.toRaw():
self.requestAuthenticationOutput = "User pressed \"Enter Password\""

default:
self.requestAuthenticationOutput = "TouchID is not configured"
}
}

}
}
else {
switch authError!.code {
case LAError.TouchIDNotAvailable.toRaw():
self.requestAuthenticationOutput = "No Touch ID on device"
case LAError.TouchIDNotEnrolled.toRaw():
self.requestAuthenticationOutput = "No fingers enrolled"
case LAError.PasscodeNotSet.toRaw():
self.requestAuthenticationOutput = "No passcode set"
default:
self.requestAuthenticationOutput = "Something went wrong getting local auth"
}
}

self.statusLabel.text = self.requestAuthenticationOutput
}


The only thing the gets instantly executed in the cases as a way to output the results are alerts.
I'm new to iOS programming but I suspect it has to do with asynchronous code execution, but I haven't exactly understood how to use GCD/queues/etc. How could I make the last line in the function wait for the first part to finish executing? Any ideas would be greatly appreciated. (I'm using swift, but testing ObjC brought the same results)

Answer

The short answer is that you can't make the code wait until the Fingerprint authentication succeeds or fails (provided that the device is capable).

One way to get your desired effect would be to update the UI once you get the result in the success/error block. As this code is not executed on the Main (UI) thread, the code needs to get executed in the main thread using GCD.

Listed below is a function updateStatusLabel - which does this. Even if executing this in the main thread - in the case where the API is not available, it will be perfectly safe.

func updateStatusLabel()
{
   dispatch_async(dispatch_get_main_queue(), 
      {
        self.statusLabel.text = self.requestAuthenticationOutput
      })
}

var myContext:LAContext = LAContext()
var authError:NSError?
var myLocalizedRasonMessage = "Please authenticate using your fingerprint"

if (myContext.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error:&authError)) {
    myContext.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedRasonMessage) { success, error in

        // ...Other code ommitted to set self.requestAuthenticationOutput

        // Update UI
        self.updateStatusLabel()
    }
}
else {
    switch authError!.code {
    case LAError.TouchIDNotAvailable.toRaw():
        self.requestAuthenticationOutput = "No Touch ID on device"
    case LAError.TouchIDNotEnrolled.toRaw():
        self.requestAuthenticationOutput = "No fingers enrolled"
    case LAError.PasscodeNotSet.toRaw():
        self.requestAuthenticationOutput = "No passcode set"
    default:
        self.requestAuthenticationOutput = "Something went wrong getting local auth"
    }
}

  self.updateStatusLabel()
}