iosswiftswiftuiface-id

After Retrying the FaceID I see unlocked apps content in a moment


I see "Unlocked" text when I try Face ID once again. How to display empty screen or Splash Screen instead of it?

My code is the following:

import Foundation
import LocalAuthentication

class UserAuthentification{

    func authenticate(){

        let context = LAContext()
        var error: NSError?

        guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
            print("No Biometric Sensor Has Been Detected. This device does not support FaceID/TouchID.")
            return
        }

        context.evaluatePolicy(LAPolicy.deviceOwnerAuthentication, localizedReason: "Only device owner is allowed", reply: { (success, error) -> Void in

            if( success ) {
                print("FaceID/TouchID. You are a device owner!")
            } else {

                // Check if there is an error
                if let errorObj = error {
                    print("Error took place. \(errorObj.localizedDescription)")
                }
                if LAError.Code.userCancel.rawValue == -2{
                self.authenticate()
            }

            }
        })
    }
}

struct ContentView: View {


    var variable : UserAuthentification

    var body: some View {

// This text I see after retrying FaceID

                Text("Unlocked")
        .onAppear(perform: variable.authenticate)

    }

}

Thanks for answering in Advance!)


Solution

  • I would modify your UserAuthentication class so that it exposes an @Observable object to indicate the authenticated state.

    You also have a mistake in your check for a user cancelled result - You are simply checking the constant's own value. You need to check the code in the LAError

    import Foundation
    import LocalAuthentication
    
    class UserAuthenticator: ObservableObject {
    
        @Published private(set) var isAuthenticated = false
        @Published private(set) var error: Error?
    
        func authenticate(){
    
            let context = LAContext()
            var error: NSError?
    
            guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
                print("No Biometric Sensor Has Been Detected. This device does not support FaceID/TouchID.")
                return
            }
    
            context.evaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Only device owner is allowed", reply: { (success, error) -> Void in
    
                DispatchQueue.main.async {
    
                    self.error = error
    
                    if( success ) {
                        print("FaceID/TouchID. You are a device owner!")
                        self.isAuthenticated = true
                    } else {
    
                        // Check if there is an error
                        if let errorObj = error as? LAError {
                            print("Error took place. \(errorObj.localizedDescription)")
                            if errorObj.code == .userCancel {
                                self.authenticate()
                            }
                        }
                    }
                }
            })
        }
    
        func deauthenticate() {
            self.isAuthenticated = false
        }
    }
    

    You can then inject an instance of this class as a model object into your view and use the isAuthenticated property to control the view state:

    import SwiftUI
    
    struct ContentView: View {
    
        @ObservedObject var authenticator: UserAuthenticator
    
        var body: some View {
            HStack {
                if authenticator.isAuthenticated {
                    VStack {
                        Text("Welcome")
                        Button(action: {self.authenticator.deauthenticate()}) {
                            Text("Logout")
                        }
                    }
                } else {
                    VStack {
                        Text("Please authenticate").onAppear {
                            self.authenticator.authenticate()
                        }
                        if self.authenticator.error != nil {
                            Text(self.authenticator.error!.localizedDescription)
                        }
                    }
                }
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView(authenticator: UserAuthenticator())
        }
    }
    

    You will need to inject the UserAuthenticator instance from your scene delegate just as I have done in the preview provider code.

    This is a bare-bones example; you need to consider re-try limits and fallback if biometric authentication isn't available, but this should get you started.