iOS9 native dialler is shown before shouldPerformDefaultActionForPerson is called - ios

I just noticed different behaviour in my app after upgrading to iOS9. I have a view that shows the device contacts of the phone.
My code is the following:
if (... == YES)
{
ABRecordSetValue(aContact, kABPersonEmailProperty, email, &anError);
if (anError == NULL)
{
ABUnknownPersonViewController *picker = [[ABUnknownPersonViewController alloc] init];
picker.unknownPersonViewDelegate = self;
picker.displayedPerson = aContact;
picker.allowsAddingToAddressBook = YES;
picker.allowsActions = YES;
picker.alternateName = #"John Appleseed";
picker.title = #"John Appleseed";
picker.message = #"Company, Inc";
[self.navigationController pushViewController:picker animated:YES];
}
Then I use the delegate to make a few decisions
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
{
//make decisions
return YES or NO;
}
The user taps in a phone number.
In IOS8 >> Code reaches shouldContinueAfterSelectingPerson and then the native dialler appears
In IOS9 >> The native dialler appears BEFORE the code reaches shouldContinueAfterSelectingPerson.
Any way to resolve it?

I am facing the same issue. What I have noticed is, if you do certain calculations within the delegate method (it obviously takes some time to do that calculation) and the native dialer gets called.
So, to avoid this problem, I am immediately returning NO from this delegate method and performing my calculations in a different thread. This is of course a workaround, hope the issue is fixed in the next release of iOS.

Related

Correct way to handle application launching with quick actions

I am having a hard time figuring out how to get my quick actions working when I launch my app with a quick action.
My quick actions work, however, if the app was in the background and re-launched with the quick action.
When I try to launch the app straight from the quick action, the app opens as if it was launched by simply tapping the app icon (i.e. it does nothing).
Here is some code from my App Delegate.
In didFinishLaunchingWithOptions:
UIApplicationShortcutItem *shortcut = launchOptions[UIApplicationLaunchOptionsShortcutItemKey];
if(shortcut != nil){
performShortcutDelegate = NO;
[self performQuickAction: shortcut fromLaunch:YES];
}
The method called:
-(BOOL) performQuickAction: (UIApplicationShortcutItem *)shortcutItem fromLaunch:(BOOL)launchedFromInactive {
NSMutableArray *meetings = [self.fetchedResultController.fetchedObjects mutableCopy];
[meetings removeObjectAtIndex:0];
unsigned long count = meetings.count;
BOOL quickActionHandled = NO;
if(count > 0){
MainViewController *mainVC = (MainViewController *)self.window.rootViewController;
if(launchedFromInactive){
mainVC.shortcut = shortcutItem;
}
else{
UINavigationController *childNav;
MeetingViewController *meetingVC;
for(int i = 0; i < mainVC.childViewControllers.count; i++){
if([mainVC.childViewControllers[i] isKindOfClass: [UINavigationController class]]){
childNav = mainVC.childViewControllers[i];
meetingVC = childNav.childViewControllers[0];
break;
}
}
self.shortcutDelegate = meetingVC;
if ([shortcutItem.type isEqual: #"Meeting"]){
NSNumber *index = [shortcutItem.userInfo objectForKey:#"Index"];
[self.shortcutDelegate switchToCorrectPageWithIndex: index launchedFromInactive:NO];
quickActionHandled = YES;
}
}
}
The only action that needs to be performed is that my page view controller (which is embedded inside the meetingVC) should switch to a certain page with respect to the shortcut chosen.
Any ideas on what causes the shortcut to not do anything when using it to launch as opposed to re-opening the app from the background??
I came to realize I was trying to call my methods on a view controller that was not in memory yet. This was causing bizarre behavior in my app. I did have the correct approach to getting access to the view controller and then it dawned on me the possibility of trying to execute the code using GCD.
__block MeetingViewController *safeSelf = self;
contentVC = [self initializeContentViewController: self.didLaunchFromInactive withPageIndex: intIndex];
NSArray *viewControllers = #[contentVC];
dispatch_async(dispatch_get_main_queue(), ^{
[safeSelf.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
});
The above worked like magic, and the shortcuts are leading to the correct page. Using a similar approach to mine hopefully yields the desired results for anyone else who wanted to get their shortcuts working by launching the app.

Not finding players on Game Center. Am I testing my game wrong?

I have the game running on the Xcode simulator and also my iPhone. When I hit search they are both put into separate games, instead of one player joining the existing one. It is a turn based game, a player can take their first turn while Game Center is automatching. I am using two separate Game Center accounts with the Sandbox setting on. Any idea what I may be doing wrong? To those who have developed a Game Center supported game, how did you test it? Thanks for the help!
Some code:
- (void)findMatchWithMinPlayers:(int)minPlayers maxPlayers:(int)maxPlayers // view controller calls to find match - nothing if GC unavaiable
viewController:(UIViewController *)viewController {
if (!_enableGameCenter) return;
_matchStarted = NO; // Match not started yet
self.currentMatch = nil;
[viewController dismissViewControllerAnimated:NO completion:nil];
GKMatchRequest *request = [[GKMatchRequest alloc] init]; // Set number of players
request.minPlayers = minPlayers;
request.maxPlayers = maxPlayers;
GKTurnBasedMatchmakerViewController *mmvc = // new instance of the GKMatchmakerViewController with the given request, sets its delegate to the GameKitHelper object, and uses the passed-in view controller to show it on the screen. Shows the user to search for a random player and start a game.
[[GKTurnBasedMatchmakerViewController alloc] initWithMatchRequest:request];
mmvc.turnBasedMatchmakerDelegate = self;
[viewController presentViewController:mmvc animated:YES completion:nil];
}
#pragma mark GKTurnBasedMatchmakerViewControllerDelegate
// A peer-to-peer match has been found, the game should start
- (void)turnBasedMatchmakerViewController:(GKTurnBasedMatchmakerViewController *)viewController didFindMatch:(GKTurnBasedMatch *)match {
[viewController dismissViewControllerAnimated:YES completion:nil];
self.currentMatch = match;
GKTurnBasedParticipant *firstParticipant = [match.participants objectAtIndex:0];
if (firstParticipant.lastTurnDate == NULL) {
// It's a new game!
[delegate enterNewGame:match];
} else {
if ([match.currentParticipant.player isEqual:[GKLocalPlayer localPlayer].playerID]) {
// It's your turn!
[delegate takeTurn:match];
} else {
// It's not your turn, just display the game state.
[delegate layoutMatch:match];
}
}
}
Are you deleting old matches with a partially filled participant list generated during testing prior to starting/joining matches for a new test? You can manually remove old matches from the game center app or use loadMatchesWithCompletionHandler which "loads the turn-based matches involving the local player and creates a match object for each match" at the appropriate time in your code to identify matches for deletion. Refer to Apple's documentation to identify the steps to properly leave, end, and remove a match.

Game Center Sandbox mode display multiple leaderboards

I'm preparing to launch my first app and want to have multiple leaderboards inside my game. Currently in sandbox mode I can track and log scores into Game Center successfully. Game Center saves my scores (only if it is higher) and seems to be fully functional.
I know through Itunes Connect we have the ability to set up multiple leaderboards and it seems pretty straight forward. I still want to be able to test multiple leaderboards before publishing my game though. Is there a way to do this in sandbox mode? Currently it seems like my scores are only automatically logged into a default leaderboard. Below is the relevant code I'm using to save/access scores. Thanks!
ABGameKitHelper.m
#pragma mark - Leaderboard
-(void) reportScore:(long long)aScore forLeaderboard:(NSString*)leaderboardId
{
GKScore *score = [[GKScore alloc] initWithCategory:leaderboardId];
score.value = aScore;
[score reportScoreWithCompletionHandler:^(NSError *error) {
if (!error)
{
if(![self hasConnectivity])
{
[self cacheScore:score];
}
if (ABGAMEKITHELPER_LOGGING) NSLog(#"ABGameKitHelper: Reported score (%lli) to %# successfully.", score.value, leaderboardId);
}
else
{
[self cacheScore:score];
if (ABGAMEKITHELPER_LOGGING) NSLog(#"ABGameKitHelper: ERROR -> Reporting score (%lli) to %# failed, caching...", score.value, leaderboardId);
}
}];
}
-(void) showLeaderboard:(NSString*)leaderboardId
{
GKLeaderboardViewController *viewController = [GKLeaderboardViewController new];
viewController.leaderboardDelegate = self;
if (leaderboardId)
{
viewController.category = leaderboardId;
CCLOG(#"Going to category already created");
}
[[self topViewController] presentViewController:viewController animated:YES completion:nil];
}
MainScene.m
- (void)gameCenter {
[[ABGameKitHelper sharedHelper] reportScore:1400 forLeaderboard:#"Score"];
[[ABGameKitHelper sharedHelper] showLeaderboard:#"Score"];
}
I'm not sure if I understand your question properly, but I'll try to answer! Game Center does support multiple leaderboards:
-If you want to send a score to specific leaderboard, you just have to call the function [[ABGameKitHelper sharedHelper] reportScore:X forLeaderboard:LEADERBOARD_ID];, where X represents the score you'd like to send, and LEADERBOARD_ID is the ID of the leaderboard you want to send the score to, as specified in iTunes Connect.
-When you have multiple leaderboards, if you don't want to show just one leaderboard, but a list of them all, you should use the GKGameCenterViewController class instead. However, be careful; this ViewController has been added in iOS 6 only, so you must check which version the device is running. I am also using the ABGameKitHelper, so I've made a function to show this kind of view. Here it goes :
ABGameKitHelper.m
- (void) showGameCenter{
if (![[ABGameKitHelper sharedHelper] hasConnectivity]) return;
//Check if device runs on iOS 5
if([[[UIDevice currentDevice]systemVersion]intValue]==5)
{
//If so, we must use the GKLeaderboardViewController
GKLeaderboardViewController *leaderboard = [[GKLeaderboardViewController alloc] init];
if (leaderboard != nil)
{
leaderboard.leaderboardDelegate = self;
[[self topViewController] presentViewController:leaderboard animated:YES completion:nil];
}
}else if ([[[UIDevice currentDevice]systemVersion]intValue]>=6)
{
//if it runs on iOS 6 or higher, we use GKGameCenterViewController
GKGameCenterViewController *gameCenterController = [[GKGameCenterViewController alloc] init];
if (gameCenterController != nil)
{
gameCenterController.gameCenterDelegate = self;
gameCenterController.viewState = GKGameCenterViewControllerStateDefault;
[[self topViewController] presentViewController:gameCenterController animated:YES completion:nil];
}
}
}
And don't forget to add :
- (void) gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController{
[gameCenterViewController dismissViewControllerAnimated:YES completion:nil];
}
Using this function will allow you to show a nice view containing all your leaderboards and achievements.
Hope this helps!

Using UIImagePicker in iPad with Landscape only on iOS 6

I am making an iPad app that only supports landscape orientations, and in that app i am calling the photo Library using the following code
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
imagePicker.sourceType =
UIImagePickerControllerSourceTypePhotoLibrary;
imagePicker.mediaTypes = [NSArray arrayWithObjects: (NSString *) kUTTypeImage, nil];
self.iPadPopoverController = [[UIPopoverController alloc] initWithContentViewController:imagePicker];
[self.iPadPopoverController setDelegate:self];
[self.iPadPopoverController presentPopoverFromRect:CGRectMake(490, 638, 44, 44) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES];
However, This causes a crash with the following exception:
Uncaught exception: Supported orientations has no common orientation with the application, and shouldAutorotate is returning YES
The Reason for the Crash:
After researching I found that the crash was due to the UIImagePickerController always being displayed in Portrait mode even when the interface orientation is Landscape.
How I tried to solve it:
After reading the
iOS6 release notes and several questions previously posted; I found that the method from which the crash was originating was in the Application Delegate's
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
the obvious solution was to have it return UIInterfaceOrientationMaskLandscape|UIInterfaceOrientationMaskPortrait
however, I want my app to support Landscape only! so my first solution was to add a Boolean flag to the App Delegate and set it to YES just before I call the UIImagePickerController and set back to NO after I dismiss it.
And it worked, but I was not happy with it, it felt like a messy solution. So instead I had the method check for its caller and if it were the function that initializes the UIImagePickerController then the method would return UIInterfaceOrientationMaskPortrait.
-(BOOL) iPadPopoverCallerFoundInStackSymbols{
NSArray *stackSymbols = [NSThread callStackSymbols];
for (NSString *sourceString in stackSymbols) {
if ([sourceString rangeOfString:#"[UIViewController(PhotoImport) importPhotoFromAlbum]"].location == NSNotFound) {
continue;
}else{
return YES;
}
}
return NO;
}
That solution worked on the simulator and on the device.
Here is the weird part
After submitting my app to the store, all my users were reporting that the app was crashing whenever they would access the photo library. I looked over the crash reports and found that the crash was occurring due to the exception mentioned above. I could not understand why this was happening.
So I submitted a TSI(Technical Support Incident) to apple about this issue. They replied that the stack symbols are not human readable after the app is archived and that is why my check would always fail.
They also suggested testing on devices by archiving the project first into a .ipa file and installing it through iTunes inorder to detect issues like this in the future.
That's Great but my problem still exists
Does anyone know how to solve this problem?
In info.plist declare all orientations as supported.
You no longer need method application:supportedInterfaceOrientationsForWindow:, so it can be deleted.
Subclass root view controller
Add following methods:
- (BOOL)shouldAutorotate {
return YES;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return UIInterfaceOrientationIsLandscape(interfaceOrientation);
}
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscape;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) {
return self.interfaceOrientation;
} else {
return UIInterfaceOrientationLandscapeRight;
}
}
If you present modal view controllers and don't want them to be rotated, then you need to add this code to their root controllers too.

strange 48 byte leaks in app using ABPeoplePickerNavigationController and Core Data

At this point I'm not sure if these leaks might be CoreData related or what, since I've experienced 48 byte strdup leaks in other parts of this same app for apparently different reasons - see my other question:another stack overflow question
But, assuming no relation, I have a viewController which, based on the user selecting an option, presents an ABPeoplePicker. However, it seems like just by presenting the picker I'm leaking, regardless of choosing a contact or not.
The code for presenting the picker is:
- (void)showPeoplePickerController
{
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
picker.displayedProperties = [NSArray arrayWithObject:[NSNumber numberWithInt:kABPersonEmailProperty]];
picker.peoplePickerDelegate = self;
[self presentModalViewController:picker animated:YES];
[picker release];
}
And the delegate methods implemented as follow:
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker
{
[self dismissModalViewControllerAnimated:YES];
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
return YES;
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier
{
ABMultiValueRef emails = ABRecordCopyValue(person, property);
if(userEmailString)
[userEmailString release];
userEmailString = (NSString*)ABMultiValueCopyValueAtIndex(emails, identifier);
CFRelease(emails);
[[NSNotificationCenter defaultCenter] postNotificationName:#"recipientEmailDidUpdateNotification"
object:self];
return NO;
}
And just in case, userEmailString is a retained NSString property of the controller (meaning I could also go for self.userEmailString = blah).
These are screenshots from Instruments, reporting the leak. But notice that it thinks its the picker not being released, though I am calling release after presenting it. And I've also tried doing CFRelease() instead... but still the same.
Anyway, yep.. the leaks are in the SDK.

Resources