1

My App supports opening documents like images, pdfs from other apps. Tocuh Id is implemented as shown below, it is requested when app comes to foreground

NotificationCenter.default.addObserver(forName: .UIApplicationWillEnterForeground, object: nil, queue: .main) { (notification) in
        LAContext().evaluatePolicy( .deviceOwnerAuthenticationWithBiometrics, localizedReason: "Request Touch ID", reply: { [unowned self] (success, error) -> Void in
             if (success) {

             } else {

             }
})

Now requesting for Touch Id works fine when user opens the app from Background or relaunches. The issue occurs when the app is opened from other app like tapping on app URL, sharing documents from external app using "Copy to MyApp" option, where the AppDelegate's open url method is called as shown below

public func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    //validate and save url
    return true
}

The issue is when the app is launched from external app, the above open url method is invoked and also the UIApplicationWillEnterForeground observer is also called as expected. But in that UIApplicationWillEnterForeground observer, LAContext().evaluatePolicy fails abruptly with an error "Caller moved to background."

Note, issue can be seen on iOS 11.0.3, 11.3 whereas it is not reproducible with iOS 11.4 or <11

Rajesh Rs
  • 1,301
  • 5
  • 24
  • 48

1 Answers1

2

You need to add this when app is applicationDidBecomeActive

NotificationCenter.default.addObserver(forName: .UIApplicationDidBecomeActive, object: nil, queue: .main) { (notification) in

let context = LAContext()

var error: NSError?

if context.canEvaluatePolicy(
    LAPolicy.deviceOwnerAuthenticationWithBiometrics,
    error: &error) {

    // Device can use biometric authentication
    context.evaluatePolicy(
        LAPolicy.deviceOwnerAuthenticationWithBiometrics,
        localizedReason: "Access requires authentication",
        reply: {(success, error) in
            DispatchQueue.main.async {

                if let err = error {

                    switch err._code {

                    case LAError.Code.systemCancel.rawValue:
                        self.notifyUser("Session cancelled",
                                        err: err.localizedDescription)

                    case LAError.Code.userCancel.rawValue:
                        self.notifyUser("Please try again",
                                        err: err.localizedDescription)

                    case LAError.Code.userFallback.rawValue:
                        self.notifyUser("Authentication",
                                        err: "Password option selected")
                        // Custom code to obtain password here

                    default:
                        self.notifyUser("Authentication failed",
                                        err: err.localizedDescription)
                    }

                } else {
                    self.notifyUser("Authentication Successful",
                                    err: "You now have full access")
                }
            }
    })

}

})
Jogendar Choudhary
  • 3,476
  • 1
  • 12
  • 26
  • Had tried UIApplicationDidBecomeActive, the issue with this is, the observer is called again after the TouchId request dialog is closed. – Rajesh Rs Sep 18 '18 at 05:40
  • issue is not in `addObserver` problem in `LAContext().` – Anbu.Karthik Sep 18 '18 at 05:41
  • @Anbu.karthik: May i know what's the problem with LAContext() – Rajesh Rs Sep 18 '18 at 05:43
  • @RajeshRs- try this once your app goes to not in active state `LAContext().invalidate()` invalidte your touch ID – Anbu.Karthik Sep 18 '18 at 05:44
  • @Anbu.karthik: When LAContext().evaluatePolicy( is called app enters UIApplicationWillResignActive state, if i do lacontext.invalidate() it cancels the current touchId request since it calls LAError.SystemCancel – Rajesh Rs Sep 18 '18 at 06:08
  • I have updated my answer, can you please check and let me know what error are you getting ? – Jogendar Choudhary Sep 18 '18 at 06:14
  • @RajeshRs - LAContext() only validate the present state and return the Bool value, so dont worry about that, if we do manually `.invalidate(` also next time it will work – Anbu.Karthik Sep 18 '18 at 06:14
  • @JogendarChoudhary: had used your updated code, getting the same error "Caller moved to background" as i have mentioned in the question. – Rajesh Rs Sep 18 '18 at 06:38
  • @Anbu.karthik: i did this, NotificationCenter.default.addObserver(forName: .UIApplicationDidBecomeActive, object: nil, queue: .main) { (notification) in LAContext().evaluatePolicy( } NotificationCenter.default.addObserver(forName: .UIApplicationWillResignActive, object: nil, queue: .main) { (notification) in LAContext().invalidate() } As i said earlier, this results in continuous display of touch Id since UIApplicationDidBecomeActive will be called after the touch id dialog is shown – Rajesh Rs Sep 18 '18 at 06:46
  • with out this `NotificationCenter.default.addObserver(forName: .UIApplicationWillResignActive, object: nil, queue: .main) { (notification) in LAContext().invalidate() } ` try once – Anbu.Karthik Sep 18 '18 at 06:50
  • It's the same, it enters an infinite loop, since UIApplicationDidBecomeActive will be called once the Touch Id dialog is closed – Rajesh Rs Sep 18 '18 at 06:58
  • @JogendarChoudhary, using your solution as well results in infinite loop, since UIApplicationDidBecomeActive will be called once the Touch Id dialog is closed – Rajesh Rs Sep 18 '18 at 06:59