0

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
                         
                     }
1
  • Your code works for me as-is on iOS 17.2.1. I do note that the documentation for evaluatePolicy states that the closure is executed in an unspecified threading context so you should dispatch your state update onto the main queue. Commented Jan 21, 2024 at 22:50

1 Answer 1

0

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."
            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for this, but it still fails. When the .deviceOwnerAuthenticationWithBiometrics is processed it will reset the showPassword back to the default value. I don't get it. My target ios is 17.1. (for test) If I set showPassword=true and then process .canEvaluatePolicy(policy) it will instantly change the showPassword=false. Before I match the faceID. So strange!
@neogaz there is something else going on then that isn’t depicted in your sample. Something is changing the identity of the view.
You are correct. I went one level up in my tabview and it works, so odd ios 17 update killed something. It's been working great! Thanks for your help!!
@neogaz glad to help. Do you mind clicking on the green checkmark to accept the answer?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.