I'm working on some integration between my application and the iPhone's AddressBook. Here is the flow of my program.
User wants to import a contact
App Presents "ABPeoplePickerNavigationController" to the User.
User selects the contact they want:
Delegate Method is called:
Here is the code:
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person {
self.selectedContact = person;
[self showNewContactViewThroughController:peoplePicker withRecord:person];
NSLog(#"should continue after selecting");
return NO;
}
5: In - (void)showNewContactViewThroughController:withRecord: We create the AbNewPersonViewController.
`ABNewPersonViewController *newController = [[ABNewPersonViewController alloc] init];`
`newController.displayedPerson = person;`
`newController.newPersonViewDelegate = self;`
And then push it.
6: User hits "Cancel" to quit the ABNewPersonViewController view.
7: Check the Contacts app, the contact they selected in step 3 is gone. Poof, gone, deleted, removed.
In an attempt to fix this issue, I save the ABRecordRef (to the instance variable "selectedContact"). Then, in - (void)newPersonViewController:didCompleteWithNewPerson: I have:
if (person) {
//do stuff with the person
else {
///
/// This means they canceled, so we need to save the old person back
///
if (self.selectedContact) {
ABAddressBookRef addressBook = ABAddressBookCreate();
CFErrorRef error = NULL;
ABAddressBookAddRecord(addressBook, self.selectedContact, &error);
if (error != NULL) {
NSLog(#"Error Adding Record: %#", error);
}
ABAddressBookSave(addressBook, &error);
if (error != NULL) {
NSLog(#"AccountDetailTableViewController.newPersonViewController() The old contact was not saved successfully. %#", error);
}
self.selectedContact = nil;
}
}
However, this doesn't seem to do anything. The code is executed, but the "Old Contact", self.selectedContact, is not saved to the AddressBook. So, my contacts are still disappearing. What am I doing wrong? It appears as though if you hit Cancel in the ABNewPersonViewController, it "removes" the data it was given from the Address Book? So the person I give it then dies? Any help would be appreciated!
I solved this problem by NOT using the ABNewPersonViewController, but rather by using the ABPersonViewController after the user selected the person they want to use. The information is no longer being deleted.
Related
I am using Quickblox Api, for chat and video chat. iOS. And I am using the latest version of the API
When I try to Make a video call,
most of the times i don't get video, only audio.
i get video on both ends 1 out of 15 times.
3 out of 10 times video on one end.
very weird. I have good internet connection. connecting to chat users are receiving the call. Can seem to find out the issue.
After spending sometime to find the issue, I received and help from Quickblox Help Center.
If your face such Behavior on the API
1.Make Sure that you set Delegate Methods in viewDidLod, not view did appear or etc. For Ex:
- (void)viewDidLoad {
[super viewDidLoad];
[[QBChat instance] addDelegate:self];
[QBRTCClient.instance addDelegate:self];
[QBSettings setCarbonsEnabled:YES];
}
Use Breakpoints to find out if they are getting called, once you make or receive calls.
2.Make Sure that your Calling methods are correct. An array containing Users must not equal to currentUser.ID.
NSInteger currentUserID = [QBSession currentSession].currentUser.ID;
int count = 0;
NSNumber *currentUserIndex = nil;
for (NSNumber *opponentID in opponentsIDs) {
if ([opponentID integerValue] == currentUserID) {
currentUserIndex = #(count);
break;
}
count++;
}
if (currentUserIndex) [opponentsIDs removeObjectAtIndex:[currentUserIndex intValue]];
QBRTCSession *session = [QBRTCClient.instance createNewSessionWithOpponents:opponentsIDs
withConferenceType:QBRTCConferenceTypeVideo];
NSDictionary *userInfo = #{ #"key" : #"value" };
[session startCall:userInfo];
if (session) {
self.currentSession = session;
[self performSegueWithIdentifier:#"openDialogSeg" sender:self];
}
else {
[SVProgressHUD showErrorWithStatus:#"You should login to use chat API. Session hasn’t been created. Please try to relogin the chat."];
}
}
Check View Layout, size and width. make sure they are set correctly.
I'm implementing a turn-based game with multiplayer mode through gamecenter. I have 2 devices (1 ipad, 1 iphone) to test in sandbox mode which were working fine but lately it has started to struggle in auto matchmaking process. After I send the first turn from one user, the other device doesn't immediately recognize that game but opens up its own fresh game. Before it was able to immediately spot the game started in the other device and matchmaking was fairly straightforward. And I don't remember changing any parts relevant to matchmaking (NSCoding, GKTurnBasedEventHandler, GKTurnBasedMatchmakerViewControllerDelegate delegate methods etc).
Now I send the first turn from one device and need to wait around 1 min so the other device can successfully connect to that game. After connection occurs endTurnWithMatchData calls work without any problems, it can send and receive data within 1-2 secs. But it won't be a good UX if users start a fresh game and had to wait 1 min so another user can connect to his game. Has anyone been experiencing significant lag in auto matchmaking process? I didn't implement invitations yet, so I cannot check it. The matchdata I archive with NSKeyedArchiver seemed quite big, 3396 bytes, even for a fresh game with almost no data. And here are relevant parts of my code:
GameOptionsViewController:
- (void)turnBasedMatchmakerViewControllerWasCancelled:(GKTurnBasedMatchmakerViewController *)viewController
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)turnBasedMatchmakerViewController:(GKTurnBasedMatchmakerViewController *)viewController didFailWithError:(NSError *)error
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)turnBasedMatchmakerViewController:(GKTurnBasedMatchmakerViewController *)viewController didFindMatch:(GKTurnBasedMatch *)match
{
[self dismissViewControllerAnimated:NO completion:nil];
self.gcMatch = match;
[self performSegueWithIdentifier:#"GameMultiplayer" sender:self];
}
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"GameMultiplayer"])
{
GameViewController *GameVC = (GameViewController *)segue.destinationViewController;
[GameVC setGameMode:GAMEMODE_MULTIPLAYER_SAMEDEVICE];
//Multiplayer game it is
if(self.gcMatch != nil)
{
[GameVC setGameMode:GAMEMODE_MULTIPLAYER_GAMECENTER];
GameVC.gcMatchDelegate = self;
GameVC.gcMatch = self.gcMatch;
NSLog(#"Game OVC Segue: Match ID | %#", self.gcMatch.matchID);
}
}
else
{
...
}
}
GameViewController:
//This method is called according to user actions
//It's the only method I use to send data to other participant
-(void) sendCurrentGameDataWithNewTurn:(BOOL) newTurn
{
NSLog(#"Sending game data current participant : %#", gcMatch.currentParticipant.playerID);
//Update match data if it is corrupted anyhow
if (gcMatch.currentParticipant == nil)
{
[GKTurnBasedMatch loadMatchWithID:gcMatch.matchID withCompletionHandler:^(GKTurnBasedMatch *match, NSError *error)
{
if (error != nil)
{
NSLog(#"Error :%#", error);
return ;
}
[self sendCurrentGameDataWithNewTurn:newTurn];
}];
}
else
{
NSData *matchData = [NSKeyedArchiver archivedDataWithRootObject:game];
//Game advances to new player, buttons are disabled
if(newTurn)
{
NSLog(#"SENDING NEW TURN");
NSUInteger currentIndex = [gcMatch.participants
indexOfObject:gcMatch.currentParticipant];
GKTurnBasedParticipant *nextParticipant;
nextParticipant = [gcMatch.participants objectAtIndex:
((currentIndex + 1) % [gcMatch.participants count])];
[gcMatch endTurnWithNextParticipants:[NSArray arrayWithObject:nextParticipant] turnTimeout:GC_TURN_TIMEOUT matchData:matchData completionHandler:^(NSError *error) {
NSLog(#"Sent");
if (error) {
NSLog(#"SNT - %#", error);
}
}];
}
else
{
NSLog(#"ONLY UPDATING DATA");
[gcMatch saveCurrentTurnWithMatchData:matchData completionHandler:^(NSError *error) {
NSLog(#"Sent");
if (error) {
NSLog(#"OUD - %#", error);
}
}];
}
}
}
-(void) updateGameDataWithGCMatch
{
//Update whole game data
self.game = [NSKeyedUnarchiver unarchiveObjectWithData:self.gcMatch.matchData];
//Update game ui
...
}
-(void) handleTurnEventForMatch:(GKTurnBasedMatch *)match didBecomeActive:(BOOL)didBecomeActive
{
//Check if I got data for the currently active match that options vc forwarded me here, if not do some debug print and return
if(![self.gcMatch.matchID isEqual:match.matchID])
{
//For debugging reasons I skip if i get info for any previous match (other player quit etc)
NSLog(#"GCMatch matchID: %# match matchID: %#",self.gcMatch.matchID,match.matchID);
return;
}
NSLog(#"Turn event handle");
self.gcMatch = match;
if([match.currentParticipant.playerID isEqualToString: [GKLocalPlayer localPlayer].playerID ])
{
//Disable field buttons
[self setFieldButtonsEnabled:TRUE];
[self turnChangeAnimationFromLeftToRight:FALSE];
}
[self updateGameDataWithGCMatch];
}
As for your question:
I myself tempered with matchmaking over Game Center quite a bit and also experienced lags quite frequently, which have been proven to not have been caused by my site but by apples game center servers.
As for additional guidance:
As far as I can see your current approach to matchmaking on a device is:
look if there is a match I can connect to --> If YES request gamedata and connect to the match ELSE start your own match and broadcast the matchdata
From my experience it is better practice to start with matchrequestbroadcasts, wait until you find a second player, define the server device (e.g. by lower checksum of game-center names) and then start the game on that device.
I am using an ABNewPersonViewController to create a person. Everything works fine so far. I do set multivalue properties as well as single values. After tapping "Add" the contact can be found in the adressbook.
But there is a problem when editing this contact. After adding the contact to the adressbook, I save the addressbookID, so that I am able to identfiy if the contact is still saved in the AB.
So If the user wants to export a contact again, I do not create a new ABPersonRecordRef, but using the existing one identified by the id I have saved before:
ABRecordID recordId = [aContact.addressBookRecordId intValue];
ABRecordRef personRecord = nil;
if(recordId != 0) {
personRecord = ABAddressBookGetPersonWithRecordID(addressBook, recordId);
if(personRecord) {
return personRecord;
} else {
personRecord = ABPersonCreate();
}
} else {
personRecord = ABPersonCreate();
}
//set properties etc.
The problem now is, that this just works the first time. If the user displays the person a second time in the ABNewPersonViewController and taps on Cancel, the record will be deleted in the adress book. Although it was previously saved fine.
I tried using an ABUnknownPersonViewController, but the problem is that it seems that the attributes det on the recordRef are not displayed exactly like in the ABNewPersonViewController.
Any suggestions?
Though this thread is old, it might help others. Try to override the cancel event as shown here. It worked for me.
- (void)showNewPersonViewController:(ABRecordRef)person
{
//show new Person view controller
ABNewPersonViewController *vcNewPerson = [[ABNewPersonViewController alloc] init];
vcNewPerson.newPersonViewDelegate = self;
vcNewPerson.displayedPerson = person;
vcNewPerson.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:#selector(onNewPersonCancelClick)];
[self.navigationController pushViewController:vcNewPerson animated:YES];
[vcNewPerson release];
}
- (void)onNewPersonCancelClick
{
[self dismissViewControllerAnimated:YES completion:nil];
}
This will be of use to you:
Can ABNewPersonViewController be used to edit existing records?
It covers a tutorial with your exact problem.
I use the function [GKAchievement loadAchievementsWithCompletionHandler:] to restore the current player achievements in initialization. But the completionHander was never called.
- (void)loadAchievements
{
[GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error)
{
if (error == nil) // !!-- if a breakpoint is set here, it would never be reached
{
#synchronized(_achievementsDictionary)
{
for (GKAchievement* achievement in achievements)
[_achievementsDictionary setObject:achievement forKey:achievement.identifier];
NSLog(#"achievements loaded");
}
}
else
{
NSLog(#"Error in loading achievements: %#", error);
}
}];
}
However, a similar function, [GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:] works well:
- (void) retrieveAchievmentMetadata
{
[GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:
^(NSArray *descriptions, NSError *error) {
if (error != nil)
{
NSLog(#"Error in loading achievement descriptions: %#", error);
}
if (descriptions != nil)
{
#synchronized(_achievementsMetaDataDictionary)
{
for (GKAchievementDescription* desc in descriptions)
{
_achievementsMetaDataDictionary[desc.identifier] = desc;
}
}
NSLog(#"achievement descriptions loaded");
}
}];
}
What might be the problem?
It comes a bit late, but maybe it helps someone else.
The fact is that GKAchievement loadAchievementsWithCompletionHandler: loads all the achievements which the local player made progress on. This means, if there are fresh achievements set up in the regarding iTunes Connect app (without any progress), they won't be loaded. Some progress has to be reported first!
On the other hand GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler: is meant to get all the information about every of the available achievements for the regarding iTunes Connect app. The description provides the identifier of the achievement, too.
For a fresh achievement the flow is the following:
Load the achievement description. (GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:)
Report some progress for the achievement to Game Center. The GKAchievement can be created based on data in GKAchievementDescription. (GKAchievementDescription reportAchievements:withCompletionHandler:)
From this point on, load the progress of the achievement to set up your app on start. (GKAchievement loadAchievementsWithCompletionHandler:)
Did you check that the returned descriptions NSArray does not have 0 elements?
if( !descriptions.count )
printf( "User has not submitted _any_ progress on _any_ achievements\n" ) ;
else for (GKAchievementDescription* desc in descriptions) ..
Note the descriptions array here only returns the collection of achievements this user has previously submitted progress on, not the array of all achievements ever registered on GameCenter for this app.
I have my app give a notification when a Game Center achievement is achieved/reaches 100%, however it shows the notification every times the user completes it, but I only want it to notify the first time in actually completed.
I found this in the Apple docs:
http://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/GameKit_Guide/Achievements/Achievements.html#//apple_ref/doc/uid/TP40008304-CH7-SW11
However didn't quite understand how it help fix my issue.
I only want to call this notification once, when the achievement is first achieved. So only show it if its not already achieved.
Edit
I have this to unlock the achievement:
- (void) reportAchievementIdentifier: (NSString*) identifier percentComplete: (float) percent
{
GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier: identifier] autorelease];
if (achievement)
{
achievement.percentComplete = percent;
[achievement reportAchievementWithCompletionHandler:^(NSError *error)
{
if (error != nil)
{
// Retain the achievement object and try again later (not shown).
}
}];
}
}
And unlock with this:
[self reportAchievementIdentifier:identifier percentComplete:percent];
Then show the notification with this line:
[[GKAchievementHandler defaultHandler] notifyAchievementTitle:#"Title" andMessage:#"Message"];
So do I simply need something like this in that code chunk?
if (achievement.completed != TRUE) {
[[GKAchievementHandler defaultHandler] notifyAchievementTitle:#"Title" andMessage:#"Message"];
}
There's a "completed"-property in GKAchievement... Try to make a new GKAchievement and check if it's not completed, then unlock it.