How to set a max number of friends using FBFriendPickerViewController? - ios

I've been working with Facebook to make a multiplayer game. I want to allow the player to be able to invite his friends from Facebook to play in a match, so I use FBFriendPickerViewController for this. However, I want to limit the number of selected friends to a minimun of 1 player and a maximun of 4.
The problem is that there's no obvious way to do this, or at least none mentioned in the Developer documents at Facebook. I tried to prevent this inside
- (void)friendPickerViewControllerSelectionDidChange:(FBFriendPickerViewController *)friendPicker
but as the attribute NSArray *selection is readonly, it can't be done. I had also thought of warning the user after he had selected the friends and clicked the 'Done'button, but it's kind of lame allowing him to choose 100 friends and after that warning him about the 4 players max limit.
Does anyone have any idea how to do this? Or will I have to implement a full FBFriendPickerViewController from scratch?
Thanks! :D

One work around could be showing a label/message on the picker letting the user know they can pick up to 4 friends. Then after picking four you dismiss the view controller? Then you could add code like this:
- (void)friendPickerViewControllerSelectionDidChange:
(FBFriendPickerViewController *)friendPicker
{
if ([friendPicker.selection count] > 3) {
UIAlertView *alertView =
[[UIAlertView alloc] initWithTitle:#""
message:#"Max number of friends selected."
delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
}
- (void)alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
[self dismissModalViewControllerAnimated:YES];
}
You may be looking for a better experience, for example to give users a chance to edit from their maximum list. In that case you can get the source code from GitHub tableView:didSelectRowAtIndexPath: and tableView:didDeselectRowAtIndexPath: delegate methods in the FBGraphObjectTableSelection class. Looks like you would likely add a new "maxSelection" property and key off that.

Previous SDK was HTML based and was hosted on facebook but SDK 3.x is native iOS code with open source.
You can modify Facebook SDK. It is licensed under Apache license and add limit for friends you allow to invite.

just removing the friend picker isn't the solution! but this is!
i got it , after struggling for quite a few times.
solution was simpler than i imagined.
you friendPickerController is a tableView so we can set userInteractionEnabled property to NO.
- (void)friendPickerViewControllerSelectionDidChange:
(FBFriendPickerViewController *)friendPicker
{
if ([friendPicker.selection count] <=3)
{
self.friendPickerController.tableView.userInteractionEnabled=YES;
}
if ([friendPicker.selection count] >=3)
{
UIAlertView *maxFriendsAlert =
[[UIAlertView alloc] initWithTitle:#"Max number of friends selected."
message:#"no more friends can be selected,"
delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:#"Buy more friends",nil];
[maxFriendsAlert show];
maxFriendsAlert.tag=1;
// disable friends selection
self.friendPickerController.tableView.userInteractionEnabled=NO;
}

Replying a bit late, but I was just looking for a solution to this issue, and I went with something that I found to be a bit cleaner than the other solutions listed:
- (void)friendPickerViewControllerSelectionDidChange:(FBFriendPickerViewController *)friendPicker {
if ([friendPicker.selection count] > 3) {
friendPicker.doneButton.enabled = NO;
[[[UIAlertView alloc] initWithTitle:#"Too many selections"
message:#"You may only select up to 3 friends."
delegate:nil
cancelButtonTitle:#"Ok" otherButtonTitles:nil] show];
} else {
friendPicker.doneButton.enabled = YES;
}
}
I inform the user that they have exceeded the max (via UIAlertView), then I disable the Done button. When the count comes back down to a valid number, I re-enable the Done button.

Related

How to handle an error

I'm working on this login screens. I want the user to be notified when login fails. For example if the user enters the wrong credentials or the server which my app connects to is down it has to show a message login failed.
This is my code so far:
-(void)loginSucces
{
[self showLoginProcess:false];
PaMainViewController * vc = [[PaMainViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
-(void)loginFailed
{
//TODO: handle error
[self showLoginProcess:false];
NSLog(#"LoginVC Fail!");
}
i hope this is what u want..
Use UIAlertView if you just wants to notify the User that login is failed.
-(void)loginFailed
{
//TODO: handle error
[self showLoginProcess:false];
NSLog(#"LoginVC Fail!");
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Warning" message:#"LoginVC Fail!" delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
}
You can display UIAlertView with your message or you can use any third party toast notifications.
Best approach, when network request ends with some result, process that result and show it to user. Usually server provides some user readable information about reason of failure. Same story when for some reason server is not reachable, just take NSError and show user a localizedDescription.
Why this way. User will receive some useful information and you will have less headache with localization.
Good example: network is unavailable. In such case NSerror localizedDescription will provide nice explanation of the problem and user can do respective action like enable WiFi or go outside to restore 3g connection.

ABRecordCopyValue() EXC_BAD_ACCESS Error while getting kABPersonFirstNameProperty

I am upgrading my Application written a year ago for iOS 6 to iOS 7/8 and I am getting this EXC_BAD_ACCESS error which never occurred in my old version.
In my application I am trying to fetch certain contact information like first name, last name, phone numbers, photo. Application flow is as follow:
1) Click on a button, presents address book.
2) Select any contact.
3.1) If contact has only one phone number, update the label.
3.2) If contact has multiple phone number, represent them in action sheet and whatever number user selects update that number to UILabel.
Now, if a contact has a single phone number application works fine without crash. i.e. 1-->2-->3.1 path. But if a contact has multiple phone and as soon as one contact number is selected from action sheet it crashes at this line.
CFTypeRef firstNameCF = (__bridge CFTypeRef)(CFBridgingRelease(ABRecordCopyValue(sharedSingleton.personGlobal, kABPersonFirstNameProperty)));
Detail Code
1) Select a contact
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person {
sharedSingleton.personGlobal = nil;
sharedSingleton.personGlobal=person; // ====> Save a ABRecordRef object globally.
//^^^ Could this be a culprit? I tried to make it private variable also at first.
[self displayAndVerifyPerson]; // No 2 below.
[self dismissViewControllerAnimated:YES completion:^{
}];
return NO;
}
2) Will check how many phone nos person has got. 0/1/>1.
If 0 show no phone no error.
If 1 phone update label by calling updateLabel.
If >1 represent action sheet for user to select number. And on clickedButtonIndex call updateLabel.
-(void)displayAndVerifyPerson
{
ABMultiValueRef phoneNumbers = ABRecordCopyValue(sharedSingleton.personGlobal,kABPersonPhoneProperty); //ABRecordRef which globally saved.
globalContact=nil; //NSString to store selected number. Works fine.
//self.personGlobal=person;
NSArray *phoneNumberArray = (__bridge_transfer NSArray *)ABMultiValueCopyArrayOfAllValues(phoneNumbers);
CFRelease(phoneNumbers);
if (ABMultiValueGetCount(phoneNumbers) > 0){ //Check if a contact has any number
NSLog(#" Number--> %#",phoneNumberArray); //Prints numbers correct whether no of contacts are 0/1/>1.
if ([phoneNumberArray count]==1){ //If exactly one contact number no problem.
globalContact = [phoneNumberArray objectAtIndex:0];
NSLog(#"--> %#",globalContact);
[self updateLabel]; // No 3 Below.
}
// We have multiple numbers so select any one.
else{
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:#"Select Number"
delegate:self
cancelButtonTitle:nil
destructiveButtonTitle:nil
otherButtonTitles:nil];
actionSheet.delegate=self;
actionSheet.tag=0;
for(int i=0;i<[phoneNumberArray count];i++){
[actionSheet addButtonWithTitle:[phoneNumberArray objectAtIndex:i]];
}
[actionSheet addButtonWithTitle:#"Cancel"];
actionSheet.destructiveButtonIndex = actionSheet.numberOfButtons - 1;
actionSheet.actionSheetStyle = UIActionSheetStyleBlackTranslucent;
UIWindow* window = [[[UIApplication sharedApplication] delegate] window];
if ([window.subviews containsObject:self.view])
[actionSheet showInView:self.view];
else
[actionSheet showInView:window];
}
}
else{ //No contact found. Display alert.
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"No contact numebr found."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[av show];
return;
}
}
3) Fetch first name, Last name, Image from ABRecordRef Object.
-(void)updateLabel{
// ----------------- Get First Name From Global ABRecordRef personGlobal---------------------
CFTypeRef firstNameCF = (__bridge CFTypeRef)(CFBridgingRelease(ABRecordCopyValue(sharedSingleton.personGlobal, kABPersonFirstNameProperty)));
^^^^^^^^^^^^^^^^^^^^^^
Crashes only when `updateLabel` called from Actionsheet delegate `clickedButtonAtIndex`
NSString *fName = (NSString *)CFBridgingRelease(firstNameCF);
if ([fName length]==0){
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Contact name not found."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[av show];
return;
}
self.lblFirstName.text = fName; //Set label with first Name.
self.lblHomePhone.text = self.globalContact;//Set number label.
}
4) Actionsheet Delegate
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
NSString *buttonTitle=[actionSheet buttonTitleAtIndex:buttonIndex];
if(actionSheet.tag==0){
//Printing multiple phone numbers which works and prints perfect.
NSLog(#"Lets see what you got: ===> %#",buttonTitle);
if([buttonTitle isEqualToString:#"Cancel"])
return;
globalContact=buttonTitle; // Save contact to NSString for later use.
[self updateLabel]; // No. 3.
}
}
Extra Notes:
1) Questions I looked for solution(Just 3 of many).
i) ABRecordCopyValue() EXC_BAD_ACCESS Error
ii) EXC_BAD_ACCESS when adding contacts from Addressbook?
iii) kABPersonFirstNameProperty… trowing EXC_BAD_ACCESS
2) Sample project on dropbox if someone is generous/curious enough and wants to run and check.
3) My doubts regarding this error:
The same code works for a current App (Written for iOS 6) which is on App Store but crashes for iOS 7.
Could be due to Memory management of Core Foundation. I tried to release Core Foundation object wherever I used as ARC does not take care of them. But if that is a case then it should also crash while contact has only one phone number.
THREAD ISSUE? Since application only crashed shen contact has more than one phone number, I believe action sheet delegate method clickedButtonAtIndex running on background thread and something is going wrong? (Just a random guess!)
I have tried to make my question easy and informative at my best. Any suggestion, comment or solution will be appreciated as I have been trying to get rid of this issue for last 3 days. Thanks!
you deal with CoreFoundation:
sharedSingleton.personGlobal=person;
=>
since it isn't an arc object, you have to retain it
CFRetain(person);
sharedSingleton.personGlobal=person;
AND release it once done
- dealloc {
CFRelease(sharedSingleton.personGlobal);
}
Ignoring the weirdness of a lot of this code, the fundamental issue is that you are not retaining a value that you intend to use beyond the scope it is presented in. Specifically, I am referring to the person variable in section number 1. You don't retain this variable, and so it is free to be released at any time after the scope ends (which it likely does). Therefore, once you get around to calling updateLabel it is simply a dangling pointer. To fix this, you should make it a strong variable.
But wait a minute...that is only for Objective-C objects, so you need to do a little more decorating of the property. You can add __attribute__((NSObject)) to make this type behave as if it were an NSObject and subject to ARC. I can't find documentation about this anymore, but here is a reference from an old Apple Mailing List Thread

UIAlertView on iOS 7 Doesn't Display the title

So I am neck deep in a project, and we kept running into this problem in multiple places throughout it. When we display a UIAlertView the standard, typical way, the title doesn't display on the alert, only the description and buttons. We do the initialization and display as anyone would:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Are you sure?" message:#"Deleting a ..." delegate:self cancelButtonTitle:#"Delete" otherButtonTitles:#"Cancel", nil];
[alert show];
Has anyone else ever run into this problem? We aren't importing and dependency that changes anything with that, nor are we doing anything that we can tell would cause this.
Edit: Here's an example of what I'm seeing:
I also met the same problem. The root cause is that I used "UIFont+Replacement" in my project. In iOS 7, system will use "UIFont fontWithDescriptor:" to setup font for labels in system controls.
I added a judgment to check whether the font replacement is used for system controls like UIAlertView. Please check my code, so far it works well:
UIFontDescriptor *replaceDescriptor = descriptor;
if (![descriptor.fontAttributes objectForKey#"NSCTFontUIUsageAttribute"]
|| ![((NSString *)[descriptor.fontAttributes objectForKey:#"NSCTFontUIUsageAttribute"])
isEqualToString:#"UICTFontUsageEmphasizeBody"])
{
NSString *replacementFontName = [replacementDictionary objectForKey:descriptor.postscriptName];
replaceDescriptor = [UIFontDescriptor fontDescriptorWithName:replacementFontName size:pointSize];
}
return [self replacement_fontWithDescriptor:replaceDescriptor size:pointSize];

Alert after using sharedmanager

This is my code
audioViewController *voiceRecorder = [audioViewController sharedManager];
[voiceRecorder stopRecording];
NSString *msg = [NSString stringWithFormat:#"Want to logout?"];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Info"
message:msg
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
alert.tag = 100;
[alert show];
I am calling sharedManager in one of my view controller. The problem is, my alertview runs before sharedManager method executes, if you check my code, i have called "StopReording" method, but when i run the code, it works after showing alert. Anyone has idea, how do I show alert only after the method returns something.?
You seem to be confusing yourself about method run order and alert presentation order. The methods run in the order specified by your code, they must. What you see on screen is 2 alerts, one (stop) presented first, the the other (logout) presented immediately after.
Generally, you shouldn't show 2 alerts at the same time. Certainly not if they relate to different things.
Present your first alert, then wait for the answer to be received (using the delegate methods). Once you have the users answer, then decide what to do next and present the second alert or continue with some other operation.

Does the Twitter iOS API provide any way of determining if the tweet was successful?

I'm using the following snippet of code to make a tweet in my iOS 5 application :
- (IBAction)postToTwitterClicked:(id)sender
{
if ([TWTweetComposeViewController canSendTweet])
{
TWTweetComposeViewController *tweetSheet = [[TWTweetComposeViewController alloc]init];
[tweetSheet setInitialText:#"Some sample message here"];
[tweetSheet addURL:[NSURL URLWithString:#"http://myURL"]];
[self presentModalViewController:tweetSheet animated:YES];
}
else
{
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Unable to tweet"
message:#"Please ensure that you have at least one twitter account setup and have internet connectivity. You can setup a twitter account in the iOS Settings > Twitter > login."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[av show];
}
}
This works fine, but how do I know that the user did actually post a tweet, or if there was a problem?
Since this doesn't implement a delegate, there are no "onError" methods that I can override.
I want to know if the user did successfully post a tweet, so I can action some behaviour such as
Disable a button so they can't do it again
Notify them the post was successful and will show up in their feed shortly
There is no way in the iOS Twitter API that you can see that a Tweet actually was posted on the server. But you can analyze the TWTweetComposeViewControllerResult to see if the tweet was finished composing successfully or if the tweet was cancelled.
twitter.completionHandler = ^(TWTweetComposeViewControllerResult res) {
if (res == TWTweetComposeViewControllerResultDone) {
// Composed
} else if (res == TWTweetComposeViewControllerResultCancelled) {
// Cancelled
}
[self dismissModalViewControllerAnimated:YES];
};
Well, actually, you only can set a handler to call when the user is done composing the tweet: TWTweetComposeViewControllerCompletionHandler. This handler has a single parameter that indicates whether the user finished or cancelled composing the tweet.
You can try to send a tweet and make it fail to check the result code (luckily it's cancelled?).
Another alternative to achieve the desired behaviour is use another API.

Resources