Touch ID causing app to become non-responsive

2019-02-04 17:42发布

问题:

I Added ios-8's new touchID API to my app. It usually works as expected, BUT when entering app while my finger is already on home-button - API's success callback is called but pop-up still appears on screen. after pressing CANCEL UI becomes non-responsive.

回答1:

I also encountered the same issue, and the solution was to invoke the call to the Touch ID API using a high priority queue, as well as a delay:

// Touch ID must be called with a high priority queue, otherwise it might fail.
// Also, a dispatch_after is required, otherwise we might receive "Pending UI mechanism already set."
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.75 * NSEC_PER_SEC), highPriorityQueue, ^{
  LAContext *context = [[LAContext alloc] init];
  NSError *error = nil;

  // Check if device supports TouchID
  if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
      // TouchID supported, show it to user
      [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
              localizedReason:@"Unlock Using Touch ID"
                        reply:^(BOOL success, NSError *error) {
                            if (success) {
                                // This action has to be on main thread and must be synchronous
                                dispatch_async(dispatch_get_main_queue(), ^{
                                    ...
                                });
                            }
                            else if (error) {
                                ...
                            }
                        }];
  }
});

When testing our app, we found a delay of 750ms to be optimal, but your mileage may vary.

Update (03/10/2015): Several iOS developers, like 1Password for example, are reporting that iOS 8.2 have finally fixed this issue.



回答2:

Whilst using a delay can potentially address the issue, it masks the root cause. You need to ensure you only show the Touch ID dialog when the Application State is Active. If you display it immediately during the launch process (meaning the Application is still technically in an inactive state), then these sorts of display issues can occur. This isn't documented, and I found this out the hard way. Providing a delay seems to fix it because you're application is in an active state by then, but this isn't guarenteed.

To ensure it runs when the application is active, you can check the current application state, and either run it immediately, or when we receive the applicationDidBecomeActive notification. See below for an example:

- (void)setup
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationDidBecomeActive:)
                                                 name:UIApplicationDidBecomeActiveNotification
                                               object:nil];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    // We need to be in an active state for Touch ID to play nice
    // If we're not, defer the presentation until we are
    if([UIApplication sharedApplication].applicationState == UIApplicationStateActive)
    {
        [self presentTouchID];
    }
    else
    {
        __weak __typeof(self) wSelf = self;
        _onActiveBlock = ^{
            [wSelf presentTouchID];
        };
    }
}

-(void)applicationDidBecomeActive:(NSNotification *)notif
{
    if(_onActiveBlock)
    {
        _onActiveBlock();
        _onActiveBlock = nil;
    }
}

- (void)presentTouchID
{
    _context = [[LAContext alloc] init];
    _context.localizedFallbackTitle = _fallbackTitle;
    [_context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
             localizedReason:_reason
                       reply: ^(BOOL success, NSError *authenticationError)
     {
         // Handle response here
     }];
}


回答3:

This accepted answer does not address the underlying cause of the problem: invoking evaluatePolicy() twice, the second time while the first invocation is in progress. So the current solution only works sometimes by luck, as everything is timing dependent.

The brute-force, straightforward way to work around the problem is a simple boolean flag to prevent subsequent calls from happening until the first completes.

AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
if ( NSClassFromString(@"LAContext") && ! delegate.touchIDInProgress ) {
    delegate.touchIDInProgress = YES;
    LAContext *localAuthenticationContext = [[LAContext alloc] init];
    __autoreleasing NSError *authenticationError;
    if ([localAuthenticationContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authenticationError]) {
        [localAuthenticationContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:kTouchIDReason reply:^(BOOL success, NSError *error) {
            delegate.touchIDInProgress = NO;
            if (success) {
                ...
            } else {
                ...
            }
        }];
    }


回答4:

I started getting the "Pending UI mechanism already set." error mentioned as well, so I decided to see if other apps were affected. I have both Dropbox and Mint set up for Touch ID. Sure enough Touch ID wasn't working for them either and they were falling back to passcodes.

I rebooted my phone and it started working again, so it would seem the Touch ID can bug out and stop working. I'm on iOS 8.2 btw.

I guess the proper way to handle this condition is like those apps do and fallback to password / passcode.