iosswiftuiface-idlocalauthentication

Swift LocalAuthentication issue with faceID resetting my state vars. Stopped working with iOS 17


I'm trying to change a state bool var with faceID.

My Test Code.

import SwiftUI
import LocalAuthentication

struct PasswordEditTest: View {
    
    @State private var showPassword: Bool = false
  
    var body: some View {
        
        HStack {
            Section(header: Text("Password:").foregroundColor(Color.theme.red)
            ) {
                
                if  showPassword {
                    Text("UNLOCKED")
                }
                else {
                    Text("LOCKED")
                }
            }
            
            Spacer()
             
             Image(systemName: showPassword ? "lock.open" : "lock")
                 .resizable()
                 .scaledToFill()
                 .frame(width: showPassword ? 30 : 30, height: showPassword ? 39 : 39)
                 .padding()
                 .onTapGesture {
                     
                     let context = LAContext()
                     var error: NSError?
                    
                     if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
                         let reason = "We need to unlock your password"
                         
                         context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
                                 if success {
                                     
                                     print("SUCCESS")
                                     showPassword=true
                                    
                                 } else {
                                     
                                     print("FAIL")
                                     
                                     showPassword=false
                                    
                                 }
                             
                         }
                     } else {
                        //NO FACEID/Biometrics
                         
                     }
                     
                     
                 }
            
        }
        
    }
    
}

If I change the showPassword to TRUE on the tap gesture, once the faceid success fires it will change the var back to false, So strange. It was working fine on iOS16

.onTapGesture {
                     showPassword=true //ADD THIS

                     let context = LAContext()
                     var error: NSError?
                    
                     if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
                         let reason = "We need to unlock your password"
                         
                         context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
                                 if success {
                                     
                                  // the showPassword will change back to false. 

                                     print("SUCCESS")
                                     showPassword=true.  // This will not change it to true Crazy!
                                    
                                 } else {
                                     
                                     print("FAIL")
                                     
                                     showPassword=false
                                    
                                 }
                             
                         }
                     } else {
                        //NO FACEID/Biometrics
                         
                     }

Solution

  • You can switch to the async/await version, it will ensure that the result is received in the MainActor

    import SwiftUI
    import LocalAuthentication
    
    struct PasswordResetTest: View {
        @State private var showPassword: Bool = false
        @State private var isEvaluatingPolicy: Bool = false
        var body: some View {
            
            HStack {
                Section(header: Text("Password:").foregroundColor(Color.red)
                ) {
                    
                    if  showPassword {
                        Text("UNLOCKED")
                    }
                    else {
                        Text("LOCKED")
                    }
                }
                
                Spacer()
                
                Image(systemName: showPassword ? "lock.open" : "lock")
                    .resizable()
                    .scaledToFill()
                    .frame(width: showPassword ? 30 : 30, height: showPassword ? 39 : 39)
                    .padding()
                    .onTapGesture {
                        //Start evaluating policy
                        isEvaluatingPolicy.toggle()
                    }
                    .task(id: isEvaluatingPolicy) {
                        guard isEvaluatingPolicy else {return}
                        let context = LAContext()
                        let policy: LAPolicy = .deviceOwnerAuthenticationWithBiometrics
                        do {
                            try context
                                .canEvaluatePolicy(policy)
                            showPassword = try await context
                                .evaluatePolicy(policy, localizedReason: "We need to unlock your password")
                            print("Was successful = \(showPassword.description)")
                        } catch {
                            print(error) //Should display error to user.
                        }
                        isEvaluatingPolicy = false //Done evaluating policy
                    }
            }
        }
    }
    

    The referenced canEvaluatePolicy is a simple conversion of the existing methodology.

    extension LAContext {
        /// Assesses whether authentication can proceed for a given policy.
        /// - Parameter policy: The policy to evaluate. For possible values,
        func canEvaluatePolicy(_ policy: LAPolicy) throws {
            var error: NSError?
            if self
                .canEvaluatePolicy(policy, error: &error) {
                //Done
            } else if let error {
                throw error
            } else {
                throw LocalError.cannotBeEvaluated(policy)
            }
        }
        
        private enum LocalError: LocalizedError {
            case cannotBeEvaluated(LAPolicy)
            var errorDescription: String?{
                switch self {
                case .cannotBeEvaluated(let lAPolicy):
                    return "Policy cannot be evaluated."
                }
            }
        }
    }