iOS 8 Touch ID error "User interaction is required." - ios

I have been working on integrating Touch ID support into an app I am working on. It is however acting very inconsistent. One of the common issues I am seeing is on a fresh app launch it works as expected, but then on backgrounding the app, and bringing it to the foreground I am getting an error back from
evaluatePolicy:localizedReason:reply:
It does not even make a lot of sense (I never see the touchid alert)
Error Domain=com.apple.LocalAuthentication Code=-1004 "User interaction is required." UserInfo=0x171470a00 {NSLocalizedDescription=User interaction is required.}
I have tried presenting the touchid alert when the app is already running, when its just foregrounded, does not seem to matter. Its broken on every time after the initial app launch.
Anyone else running into this?
For reference, here is the code I am using:
if (_useTouchId && [LAContext class]) {
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
_didPresentTouchId = YES;
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:#"Use your Touch ID to open *****" reply:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^ {
if (success) {
_isClosing = YES;
[self hide];
if (_successBlock) {
_successBlock();
}
}
else if (error && error.code != -2 && error.code != -3 && error.code != -1004) {
[[[UIAlertView alloc] initWithTitle:#"Error" message:#"Authentication failed, please enter your Pin" delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles:nil] show];
}
else {
if (error) {
DDLogError(#"TouchID error: %#", error.description);
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, .6 * NSEC_PER_SEC), dispatch_get_main_queue(), ^ {
[self keyboardButtonTouched];
});
}
});
}];
}
}

Usually PIN view controllers are pushed before entering background in:
- (void)applicationDidEnterBackground:(UIApplication *)application
So app's inner information won't appear when paging through app preview images (home button double tap). I guess you are doing something similar.
The problem is that LocalAuthentication's new API requires the calling viewController to be visible.
This is why you shouldn't call your "showTouchID" function before resigning to background. Instead call "showTouchID" function when entering foreground:
- (void)applicationWillEnterForeground:(UIApplication *)application
And it should work.
Don't forget to call it also when app is first launched (in which case ..willEnterForeground will not get called).

#hetzi answer really helped me, but I have more to add on this.
Basically this error happens when your app is woken up from background and somewhere on your code you are asking for Touch ID (my case is the local authentication type, I haven't tested with the keychain type). There's no way the user can interact with Touch ID prompted while the app is running on background, hence the error message.
User interaction is required.
The reasons my app was coming from background were: Push Notifications or Apple Watch.
My fix is doing something like this on the viewDidLoad method of my initial VC:
if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) {
[self promptForTouchID];
}
I've used != because, when your app first launches it is in the UIApplicationStateInactive state. And that state doesn't generate a Touch ID error because the prompt will appear.
I also call [self promptForTouchID] on a notification of UIApplicationWillEnterForegroundNotification, but since you know that the app will enter foreground, there's no need to check here.

Related

iOS app stuck after push notification is received in UIApplicationStateInactive mode

I have this really simple flow, where a notification is received when the app is in killed mode and the user taps on that notification.
Logic will append two controllers to the array of current viewControllers in the rootNavigationController. So, if first view was the usual screen for my app, in this case it will be first>second>third.
If you open the app with the app-icon, you'll only see the first view.
Issue is, when you tap on the notification, third view has an async API call, which is invoked from viewDidLoad. The SVProgressHUD is used for ux purpose, which is hidden in the completion block of the API. Sometimes, the progressHUD keeps on rotating and nothing happens. It gets stuck, but If I set the breakpoint in the API call method itself, everything works fine every time.
Not able to figure out, how to debug this issue. Tried pausing the execution when it gets stuck, but no relevant thread seems to be in the list.
CODE in third VC:
- (void) fetchDataPoints
{
//[SVProgressHUD show];
[[ServerHandler sharedInstance]fetchDataFromServerWithApiUrlString:GET_ALL_DATAPOINTS methodType:#"GET" httpBodyData:nil contentType:nil otherHeaderFields:nil queryStringParams:nil withCompletionBlock:^(BOOL success, NSData *responseData, NSError *error, NSHTTPURLResponse *response,NSDictionary *responseDict)
{
[SVProgressHUD dismiss];
if (success)
{
}
}];
}
CODE: push notification
if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateInactive)
{
ChildViewController *childList = ViewControllerWithSBID (#"DATASB",#"ChildScreenID");
DetailViewController *detailVC = ViewControllerWithSBID(#"DATASB", #"DetailScreenID");
NSMutableArray *viewControllers = [self.rootNavigationController.viewControllers mutableCopy];
[viewControllers addObjectsFromArray:#[childList,detailVC]];
self.rootNavigationController.viewControllers=[[NSArray alloc]initWithArray:viewControllers];
}

Pop ups are not showing

I am developing an app that needs to access both the user's location and calendar, a pop up should appear but for some reason it is not showing and I keep getting denied, here is the code for the calendar access :
- (void)askAuthorization
{
EKAuthorizationStatus authorizationStatus = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent];
BOOL needsToRequestAccessToEventStore = (authorizationStatus == EKAuthorizationStatusNotDetermined);
if (needsToRequestAccessToEventStore) {
[self.store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (granted) {
// Access granted
NSLog(#"User granted access");
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate userDidAuthorizeCalendarReadingsWithError:error];
});
} else {
// Denied
NSLog(#"User did not grant access. To grant access, go to Settings > Privacy > Calendars > VirtualAssitant");
[self.delegate userDidNotAuthorizeCalendarReadingsWithError:error];
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate userDidAuthorizeCalendarReadingsWithError:error];
});
}
}];
}
}
I am not getting a pop up and AuthorizationStatusForEntityType:EKEntityTypeEvent value is "denied"!
As for the location I went to settings-> privacy-> location, found it set to never for my application and I had to manually change it to always.
does anyone know why or has been through the same problem and can help me.
I tried cleaning the build folder and reseting Location and Privacy settings but when I rebuild my application the same thing happen I don't get a pop up asking for permission and the values are set to denied!
The permission pop up for any system access like camera, photo album, location, contacts, etc... will only appear once (for each access type).
Once it has been presented and then accepted or denied then it will never appear again.
Because it has come back with the permission "denied" this means it has already appeared and you tapped on "do not allow".
The only way to change the permission in the app after that is to go into the app settings and change it manually.
You can force the permission pop-up to appear again by deleting the app from your device and then installing it again.

How to detect privacy permission changes (Camera access for example)

We've all been there. You want to take photos within your app, or access photos, microphone, contacts, etc... But first iOS must prompt the user for permission. In many cases the user will deny access.
If your app detects that the user has denied access, you can navigate the user to the App's privacy settings with this:
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
Handy. However....
I've noticed that if you do convince the user to toggle the switch to on, that the app does not detect the changes.
Consider this code. The user is immediately prompted for permission to access the camera (this only shows the first time that app is run). Suppose the user denied permission. Next they decide that they did want to enable camera access after all. No problem. The user taps on the button which brings up the privacy panel. The user changes the switch to allow access. Then the user switches back to the app. The block fires for UIApplicationDidBecomeActiveNotification which reads the permission again. However it does not reflect the user's changes (still reads as Denied).
If the app is purged from memory and run again, it will properly read the state.
Not all permissions behave this way. For instance CoreLocation seems to detect the user's changes. I've also found a way to detect changes for Notifications. But for Contacts, Calendars, Camera, Microphone, Core Motion (and more) the changes are not detected until the app is terminated and run again.
Any ideas?
#import "ViewController.h"
#import AVFoundation;
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self printPermission];
}];
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
[self printPermission];
}];
}
-(void)printPermission{
dispatch_async(dispatch_get_main_queue(), ^{
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if(status == AVAuthorizationStatusNotDetermined){
NSLog(#"VWWPermissionStatusNotDetermined");
self.view.backgroundColor = [UIColor whiteColor];
} else if(status == AVAuthorizationStatusAuthorized){
NSLog(#"VWWPermissionStatusAuthorized");
self.view.backgroundColor = [UIColor greenColor];
} else if(status == AVAuthorizationStatusDenied) {
NSLog(#"VWWPermissionStatusDenied");
self.view.backgroundColor = [UIColor redColor];
} else if(status == AVAuthorizationStatusRestricted) {
NSLog(#"VWWPermissionStatusRestricted");
self.view.backgroundColor = [UIColor redColor];
}
});
}
- (IBAction)buttonAction:(id)sender {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
}
#end
So, this turned out to be a bug related to iOS 9b1.
Permission detection works fine on iOS 8.
I did learn that you need to check the permission on the main queue. If you do that, it will reflect updates.

Could not connect to the Game Center Server

I am trying to integrate Game Centre to my Sprite Kit game. I am referring to this link:
http://www.appcoda.com/ios-game-kit-framework/
I have created a record on my iTunes Connect account for the game I am building. I try connecting to the Game Centre in the Simulator by trying to login through the Game Centre App itself and it gives me this error:
Could not Connect to the Game Center Server
And if I try to login through settings > Game Center, I get an error saying:
Unable to connect to server,
The network connection was lost
When I open my game, I have code written which checks if there is an authenticated user or not, and if not, it shows up the login to game centre scene. It showed this a couple of times but I canceled it because I kept getting the first error. Now that screen doesn't even show up.
What am I doing wrong?
PS: I have Game Centre enabled in my game in the capabilities menu.
EDIT:
This is in my viewController.m:
-(void)authenticateLocalPlayer {
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error){
if (viewController != nil) {
[self presentViewController:viewController animated:YES completion:nil];
}
else{
if ([GKLocalPlayer localPlayer].authenticated) {
gameCenterEnabled = YES;
// Get the default leaderboard identifier.
[[GKLocalPlayer localPlayer] loadDefaultLeaderboardIdentifierWithCompletionHandler:^(NSString *leaderboardIdentifier, NSError *error) {
if (error != nil) {
NSLog(#"%#", [error localizedDescription]);
}
else{
leaderboardIdentifier = leaderboardIdentifier;
}
}];
}
else{
gameCenterEnabled = NO;
}
}
};
}
I call it in the viewDidLoad of my viewController.m
Issue might be with your device and not your code. Try these steps:
Press and hold the Sleep/Wake button and the Home button together for at least ten seconds, until the Apple logo appears.
If that doesn't help, tap Settings -> General -> Reset -> Reset Network Settings.
You will have to re-enter your WiFi password.
Also confirm your device Apple ID matches your developer ID.

Can I manually prompt the user to log in to Game Center on iOS 7?

According to Apple's Game Center programming guide, this code sets up an authentication handler. If you run this at the beginning of your game, the first time you run it, it will prompt the user to log in if they haven't yet.
- (void)authenticateLocalPlayer {
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error){
if (viewController != nil) {
NSLog(#"Player not authenticated.");
} else if (localPlayer.isAuthenticated) {
NSLog(#"Authentication successful.");
} else {
NSLog(#"Authentication failed. Error: %#.",error);
}
};
}
Suppose that the user hasn't logged in yet, and cancels the authentication screen to play the game normally.
There is a button for playing a multiplayer match in my game. If the user presses the button, it will attempt to search for other players by presenting a GKMatchmakerViewController instance.
Since the player isn't logged in, the player will actually receive an error dialog saying that they aren't logged in. The dialog only has an OK button, which dismisses it.
If the player insists on pressing this button, the same dialog will come.
However, this is an odd behaviour. It would be more reasonable that if the player wants to play a multiplayer match but isn't logged in yet, the game will prompt the user to log in.
The above code sets up a handler, so it really isn't what I'm looking for. However, I made a breakpoint and noticed that viewController is a GKHostedAuthenticateViewController instance. I figured that maybe I could create an instance of that class and present it, which should technically be equivalent to prompting the user to log in.
However, Xcode doesn't seem to recognise that class when I write it. I'm under the impression that I'm not allowed to do this.
How can I manually prompt the user to log in to Game Center?
You can first check if the player is authenticated or not, by reading the GKLocalPlayer object.
If there is not authenticated user, you can open the game centre app. The downside with this method is that after the user authenticates through the game centre app, he is still in the game centre app and has to "switch back" to your app. When he switches back, the authentication handler you defined in your code gets triggered.
-(void)clickedOnStartGame
{
if (_signedIn)
{
//Do what you need to.
}
else if (!_signedIn)
{
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:#"Game Center"
message:#"If Game Center is disabled try logging in through the Game Center app"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:#"Open Game Center", nil];
[alertView show];
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"gamecenter:"]];
}
}
EDIT: Note, that in Apple's documentation, they say that you shouldn't be prompting the user to log in again, or show a login prompt. The automated way, (which your code already has) is supposed to be the accepted way. Showing the alert view I described above just helps the user log into game centre since you aren't supposed to force the app to show a dialogue.

Resources