How can i create a custom invitation which will display the discoveryInfo from the advertiser?
Here is the code from my advertiser:
// create Discovery Info
NSArray *objects=[[NSArray alloc] initWithObjects:#"datguy",#"28", nil];
NSArray *keys = [[NSArray alloc] initWithObjects:#"Name",#"Age", nil];
self.dictionaryInfo = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
// Setup Advertiser
self.advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType:#"txt_msg_service" discoveryInfo:self.dictionaryInfo session:self.advertiseSession];
[self.advertiser start];
i think you got it backwards. IFAIK, the discoveryInfo of the MCAdvertiserAssistant is the information passed from the advertiser for the browsers.
This information is passed to:
- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info
which is a delegate method for the MCNearbyServiceBrowser class. And to this:
– browserViewController:shouldPresentNearbyPeer:withDiscoveryInfo:
Which is the delegate for the MCBrowserViewController class.
This information should be used to check if this advertiser should be displayed for your user.
If you are interested in intercepting an invitation attempt, you should consider using the MCNearbyServiceAdvertiser class instead of the assistant. This way you can delegate the didReceiveInvitationFromPeer method and add some custom logic to it:
// pedido para entrar na sala.
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID
withContext:(NSData *)context
invitationHandler:(void (^)(BOOL accept, MCSession *session))invitationHandler {
// get the browser's info
NSDictionary *peerInfo = (NSDictionary *) [NSKeyedUnarchiver unarchiveObjectWithData:context];
//check if peer is valid for this room (add your custom logic in this method)
if ([self isValidForThisRoom:peerInfo]) {
//create an alert
NSObject *clientName = [peerInfo valueForKey:#"playerName"];
NSString *clientMessage = [[NSString alloc] initWithFormat:#"%# wants to connect. Accept?", clientName];
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:#"Accept Connection?"
message:clientMessage
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[alertView show];
// copy the invitationHandler block to an array to use it later
ArrayInvitationHandler = [NSArray arrayWithObject:[invitationHandler copy]];
} else {
// if the peer is not valid, decline the invitation
invitationHandler(NO, _session);
}
}
On the clickedButtonAtIndex delegate for the alertview, you can do something like this:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
//obtem a decisao do usuario
BOOL accept = (buttonIndex != alertView.cancelButtonIndex) ? YES : NO;
// obtem o invitationHandler que foi guardado do didReceiveInvitationFromPeer
void (^invitationHandler)(BOOL, MCSession *) = [ArrayInvitationHandler objectAtIndex:0];
// chama o invitationHandler passando a nossa session
invitationHandler(accept, _session);
}
Two important things to notice is that:
Your session object should be an instance variable, not local as i saw in some code samples. In my experience, i just created a static shared instance to store the session object. This is also a good plan if you want to pass the session from one screen to another.
Notice that you should create an NSArray *ArrayInvitationHandler; variable to store the block code. I tried to do Block_copy but it had some conversion errors going on so someone here in stackoverflow decided to store in an array, which i think is elegant enough.
Anyway, i got it working using this setup. Hope it helps you :D
Related
I hope I am not violating NDA by posting this question.
I am using the new multipeer connectivity to send using bluetooth some files to nearby devices. I have managed to send invitations, but I don't seem to get how to display a UIAlertView where the user can accept or decline the invite. Right now when a user sends, the file is automatically saved and there is no accept/decline alert.
The code is:
- (void) advertiser:(MCNearbyServiceAdvertiser *)advertiser
didReceiveInvitationFromPeer:(MCPeerID *)peerID
withContext:(NSData *)context
invitationHandler:(void(^)(BOOL accept,
MCSession *session))invitationHandler{
... save the data context
but with the alert:
- (void) advertiser:(MCNearbyServiceAdvertiser *)advertiser
didReceiveInvitationFromPeer:(MCPeerID *)peerID
withContext:(NSData *)context
invitationHandler:(void(^)(BOOL accept,
MCSession *session))invitationHandler{
DevicePeer = [MCPeerID alloc];
DevicePeer = peerID;
ArrayInvitationHandler = [NSArray arrayWithObject:[invitationHandler copy]];
// ask the user
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:#""
message:#""
delegate:self
cancelButtonTitle:#"NO"
otherButtonTitles:#"YES", nil];
[alertView show];
alertView.tag = 2;
}
and the alert view method:
- (void) alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
// retrieve the invitationHandler
// get user decision
BOOL accept = (buttonIndex != alertView.cancelButtonIndex) ? YES : NO;
// respond
MCSession *session = [ArrayInvitationHandler objectAtIndex:0];
void (^invitationHandler)(BOOL, MCSession *) = [ArrayInvitationHandler objectAtIndex:0];
invitationHandler(accept, session);
}
When the user press YES the app crashes and I get the error:
[__NSMallocBlock__ nearbyConnectionDataForPeer:withCompletionHandler:]: unrecognized selector sent to instance 0x14d4e3b0'
I have looked up at the IOS developer library and there is no such method apart from
- (void)nearbyConnectionDataForPeer:(id)arg1 withCompletionHandler:(id)arg2{
}
which does no work. No info on the IOS developer forums. Any ideas?
Alessandro is right, this is not explained in the WWDC 2013 video. I struggled with this myself.
I think you're on the right track, you just have a couple logic errors.
I don't understand these two lines:
MCSession *session = [ArrayInvitationHandler objectAtIndex:0];
void (^invitationHandler)(BOOL, MCSession *) = [ArrayInvitationHandler objectAtIndex:0];
The object stored in your array is just your handler. The reason you're getting that crash is that the browser is seeing that accept is true and trying to connect the peer to the session, but the session you're giving it back is nil. To fix this, you want to pass back a new session that you create.
At first I was confused by the notion of creating a new session when one has already been created by the browser side, but then I realized that we don't get that session from the browser anywhere, and we can't really pass it back into the invitation handler if it doesn't exist!
So yeah, do this instead:
BOOL accept = (buttonIndex != alertView.cancelButtonIndex) ? YES : NO;
// respond
MCSession *session;
if(accept) {
session = [[MCSession alloc] initWithPeer:peer];
session.delegate = self;
}
void (^invitationHandler)(BOOL, MCSession *) = [ArrayInvitationHandler objectAtIndex:0];
invitationHandler(accept, session);
I suggest you to watch Nearby Networking with Multipeer Connectivity WWDC 2013 video on the Apple developers center. There is an example about this stuff and this is very well explained.
PS : Yes you break NDA (september 14) but now it's ok :)
Alright, this involves a lot of network coding from this part of a multiplayer tutorial.
Basically, I'm trying to implement a multiplayer game using GameKit as per the tutorial linked above. I put in all of the necessary network coding and more or less understand it, however I've hit a snag somewhere along the line of method calls. Basically, the setup that I have is that one device acts as the host and the rest act as the clients. I have two separate UIViewcontrollers for the host and clients respectively where the connection is established.
Now the thing is, the connection gets established, but it's only the host that recognizes the connection, not the client. The problem is here:
- (void)sendPacketToAllClients:(Packet *)packet
{
[_players enumerateKeysAndObjectsUsingBlock:^(id key, Player *obj, BOOL *stop)
{
obj.receivedResponse = [_session.peerID isEqualToString:obj.peerID];
}];
GKSendDataMode dataMode = GKSendDataReliable;
NSData *data = [packet data];
NSError *error;
if (![_session sendDataToAllPeers:data withDataMode:dataMode error:&error])
{
NSLog(#"Error sending data to clients: %#", error);
}
}
This is implemented in GameMultiplayer, where the actual game will be implemented. What this method is supposed to be doing is sending data packets to each of the clients saying that the host received the connection request and is able to connect with them. After [_session sendDataToAllPeers:data withDataMode:dataMode error:&error] is called (the method in the if statement), this method is supposed to be triggered:
- (void)receiveData:(NSData *)data fromPeer:(NSString *)peerID inSession:(GKSession *)session context:(void *)context
{
#ifdef DEBUG
NSLog(#"Game: receive data from peer: %#, data: %#, length: %d", peerID, data, [data length]);
#endif
Packet *packet = [Packet packetWithData:data];
if (packet == nil)
{
NSLog(#"Invalid packet: %#", data);
return;
}
Player *player = [self playerWithPeerID:peerID];
if (player != nil)
{
player.receivedResponse = YES; // this is the new bit
}
if (self.isServer)
[self serverReceivedPacket:packet fromPlayer:player];
else
[self clientReceivedPacket:packet];
}
This method is in the next part of the tutorial I linked above (which is here) and is supposed to receive the packets that the host sends to all clients and implement the next methods in this networking chain. However, the method never gets called. No debug breakpoints are triggered and I get nothing in the console.
I understand if I need to provide more source material, but there is a lot of network coding already implemented, so I want to keep it down to what people need to see. Also, [_session setDataReceiveHandler:self withContext:nil] and _session.delegate = self are written in another method that is called in GameMultiplayer, so that's not the problem. Does anyone know what I need to fix?
EDIT: As requested, here's where GKSession is initialized:
#property (nonatomic, strong, readonly) GKSession *session; //This is done in the header file
#synthesize session = _session; //This is done in the main file
- (void)startAcceptingConnectionsForSessionID:(NSString *)sessionID
{
if (_serverState == ServerStateIdle)
{
_serverState = ServerStateAcceptingConnections;
_connectedClients = [NSMutableArray arrayWithCapacity:self.maxClients];
_session = [[GKSession alloc] initWithSessionID:sessionID displayName:nil sessionMode:GKSessionModeServer];
_session.delegate = self;
_session.available = YES;
}
}
The session is initialized in MatchmakingServer, which is used in the host view controller. The session is then passed on to the main view controller of the app, which then initializes GameMultiplayer and sends the GKSession to it. Here's where the host view controller sends it to the main view controller:
- (IBAction)startAction:(id)sender
{
if (_matchmakingServer != nil && [_matchmakingServer connectedClientCount] > 0)
{
NSString *name = [self.nameTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([name length] == 0)
name = _matchmakingServer.session.displayName;
[_matchmakingServer stopAcceptingConnections];
[self.delegate hostViewController:self startGameWithSession:_matchmakingServer.session playerName:name clients:_matchmakingServer.connectedClients];
}
}
and then the main view controller handles that method call here:
- (void)hostViewController:(MatchmakerHost *)controller startGameWithSession:(GKSession *)session playerName:(NSString *)name clients:(NSArray *)clients
{
[self dismissViewControllerAnimated:NO completion:^
{
[self startGameWithBlock:^(GameMultiplayer *aGame)
{
[aGame startServerGameWithSession:session playerName:name clients:clients];
}];
}];
}
and finally, this is where that method call is implemented in GameMultiplayer:
- (void)startServerGameWithSession:(GKSession *)session playerName:(NSString *)name clients:(NSArray *)clients
{
_clients = clients;
const char* className = class_getName([[_clients objectAtIndex:0] class]);
NSLog(#"yourObject is a: %s", className);
self.isServer = YES;
_session = session;
_session.available = NO;
_session.delegate = self;
[_session setDataReceiveHandler:self withContext:nil];
_state = GameStateWaitingForSignIn;
[self.delegate gameWaitingForClientsReady:self];
// Create the Player object for the server.
Player *player = [[Player alloc] init];
player.name = name;
player.peerID = _session.peerID;
player.position = PlayerPositionBottom;
[_players setObject:player forKey:player.peerID];
// Add a Player object for each client.
int index = 0;
for (NSString *peerID in clients)
{
Player *player = [[Player alloc] init];
player.peerID = peerID;
[_players setObject:player forKey:player.peerID];
if (index == 0)
player.position = ([clients count] == 1) ? PlayerPositionTop : PlayerPositionLeft;
else if (index == 1)
player.position = PlayerPositionTop;
else
player.position = PlayerPositionRight;
index++;
}
NSLog(#"Players:");
Packet *packet = [Packet packetWithType:PacketTypeSignInRequest];
[self sendPacketToAllClients:packet];
// for (int i = 0; i < [_players count]; i++) {
// NSLog([NSString stringWithFormat:#"%#", [clients objectAtIndex:i]]);
// }
}
I think you are calling send to fast. When server realize about connection it will send confirmation to client to really establish connection - so client knows about it succeed.
If you are sending packets before that happens - it will be lost.
Just do this:
[self performSelector:#selector(sendPacketToAllClients) withObject:nil afterDelay:1.0];
instead of:
[self sendPacketToAllClients];
I had the same problem that connection is established in different moment with small delay on client. The best is to send first packet from client that he is ready to receive packets from server - and than proceed normally from there.
Also try debugging:
- (void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state
On both devices (server and client).
I have also had my troubles with GKSession. I was interested to learn (on this site) today that GKSession is being deprecated in favor of using the Multipeer Connectivity Framework. With luck, Wenderlich et al. will do a tutorial using the new technology. :)
The system has some similarities to GKSession, so is not too hard to wrap your head around.
Apple's doc link.
My app displays a short list of tweets based on a given TV hashtag with the additional ability to post a tweet.
But now twitter has gone oauth for all requests, my tweet list doesnt appear as it was using the old search.atom API.
Therefore, how do I access the search api and pass in OAuth credentials using Sharekit so user authenticates just the once for viewing tweets and posting between sessions.
I have tried using SHKRequest, hoping, that as ShareKit has already authorised it will pass this information through; with no joy, is there some other way of doing this or am I just using it badly/wrong.
request = [[[SHKRequest alloc] initWithURL:[NSURL URLWithString:#"https://api.twitter.com/1.1/search/tweets.json?q=twitter&result_type=recent"]
delegate:self
isFinishedSelector:#selector(sendFinishedSearch:)
params:nil
method:#"GET"
autostart:YES] autorelease];
I do need to maintain compatibility with 4.3 API so I cant just use iOS5 Twitter API.
Disclaimer: I am inheriting project from someone so my XCode/ObjC knowledge is being learnt whilst I modify project (I come from C/C++ background), so please ignore my ignorance.
ShareKit contains an SHKTwitter class. It is a subclass of SHKOAuthSharer. As such, you can ask it to perform authorisations / refreshes and get the resulting token.
Create an instance of SHKTwitter and register as it's delegate. Implement the - (void)sharerAuthDidFinish:(SHKSharer *)sharer success:(BOOL)success delegate method. Then call tokenRequest. When the delegate method is called, if success is YES you can get the accessToken.
SHKTwitter *twitter = [[SHKTwitter alloc] init];
twitter.shareDelegate = self;
[twitter tokenRequest];
- (void)sharerAuthDidFinish:(SHKSharer *)sharer success:(BOOL)success
{
SHKTwitter *twitter = (SHKTwitter *)sharer;
if (twitter.accessToken != nil) {
NSLog(#"session: %#, %#", twitterSharer.accessToken.key, twitterSharer.accessToken.secret);
} else {
[twitter tokenAccess];
}
}
IN SHKOAuthSharer.m class You will get access Token in method:
- (void)tokenAccessTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data
{
if (SHKDebugShowLogs) // check so we don't have to alloc the string with the data if we aren't logging
SHKLog(#"tokenAccessTicket Response Body: %#", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);
///[[SHKActivityIndicator currentIndicator] hide];
if (ticket.didSucceed)
{
NSString *responseBody = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
//piyush Added
NSArray *strArray = [responseBody componentsSeparatedByString:#"="];
[[NSUserDefaults standardUserDefaults]setObject:[strArray objectAtIndex:([strArray count]-1)] forKey:#"TwitterUsername"];
**self.accessToken** = [[OAToken alloc] initWithHTTPResponseBody:responseBody];
[responseBody release];
[self storeAccessToken];
//[self tryPendingAction];
[[NSNotificationCenter defaultCenter] postNotificationName:#"TwitterDidLogin" object:nil];
}
else
// TODO - better error handling here
[self tokenAccessTicket:ticket didFailWithError:[SHK error:SHKLocalizedString(#"There was a problem requesting access from %#", [self sharerTitle])]];
}
I'm using the Parse SDK to implement a Facebook apprequests dialog in my app. For all intents and purposes it just wraps the Facebook SDK, prefixing Facebook methods with PF_.
I use this code to prepare and raise the dialog:
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSString stringWithFormat:#"I just challenged you to a game!"], #"message",
[friendIds componentsJoinedByString:#"," ], #"to",
nil];
PF_FBSession *session = [PFFacebookUtils session];
PF_Facebook *facebook = [[PF_Facebook alloc] initWithAppId:session.appID andDelegate:nil];
facebook.accessToken = session.accessToken;
facebook.expirationDate = session.expirationDate;
[facebook dialog:#"apprequests" andParams:params andDelegate:self];
This works well, I'm getting the dialog, I'm able to invite friends to play with the app.
The problem is that the delegate methods are not being called, even though I've set the view controller as a PF_FBDialogDelegate:
#interface ChallengeFriendsViewController : UIViewController <UITableViewDelegate, PF_FBDialogDelegate> {
NSArray *facebookFriends;
NSMutableArray *selectedFriends;
}
These are some of the delegate methods I'm talking about:
- (void)dialog:(PF_FBDialog *)dialog didFailWithError:(NSError *)error {
NSLog(#"Error in Dialog: %#", error);
}
- (void)dialogDidNotCompleteWithUrl:(NSURL *)url {
NSLog(#"Failure on Facebook server side");
}
- (void)dialogCompleteWithUrl:(NSURL *)url {
NSLog(#"Did complete with URL");
[self.navigationController popViewControllerAnimated:YES];
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
}
- (void)dialogDidNotComplete:(PF_FBDialog *)dialog {
NSLog(#"Cancelled");
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
}
Without these methods being called I'm really not able to handle the sharing in an intuitive way. I'm stumped as to why they wouldn't be called and feel I've tried everything. Any pointers to where I'm going wrong?
In your code you have
PF_Facebook *facebook = [[PF_Facebook alloc] initWithAppId:session.appID andDelegate:nil];
Since you set a nil delegate, I would expect the delegate methods are never called, as you describe.
Could you change this to
PF_Facebook *facebook = [[PF_Facebook alloc] initWithAppId:session.appID andDelegate:self];
That is assuming that you put the delegate methods in the same class.
I finally got around the problem by using the facebook instance provided by PFFacebookUtils. This is deprecated but appears to be the only way to make it call the correct delegate methods at the moment.
Replaced:
PF_Facebook *facebook = [[PF_Facebook alloc] initWithAppId:session.appID andDelegate:nil];
With:
PF_Facebook *facebook = [PFFacebookUtils facebook];
Thanks to JP and Aromal for your input.
My app goes to a viewcontroller, makes two automatic server requests, makes the connection, retrieves the data and correctly displays it, and is done. The user clicks a "likes" button and two more server requests are made - successfully. Displays are correct. Should be done. Then it crashes, with the error:
[__NSCFNumber isEqualToString:]: unrecognized selector sent to instance
I'm using the very handy SimplePost class (by Nicolas Goles). Here are my requests, which are both called in viewDidLoad:
- (void) setScore {
Profile *newPf = [[Profile alloc] initID:thisUser profil:#"na" scor:score];
NSMutableURLRequest *reqPost = [SimplePost urlencodedRequestWithURL:[NSURL URLWithString:kMyProfileURL] andDataDictionary:[newPf toDictPf]];
(void) [[NSURLConnection alloc] initWithRequest:reqPost delegate:self];
}
- (void) saveHist {
History *newH = [[History alloc] initHistID:thisUser hQid:thisQstn hPts:score hLiked:NO];
NSMutableURLRequest *reqHpost = [SimplePost urlencodedRequestWithURL:[NSURL URLWithString:kMyHistURL] andDataDictionary:[newH toDictH]];
(void) [[NSURLConnection alloc] initWithRequest:reqHpost delegate:self];
}
The only "new" thing with my custom classes (Profile and History) is the BOOL for hLiked, but it's "working" - the database is updating correctly.
Then, the user can click a "Likes" button (+ or -). Here are the other requests:
- (IBAction)likeClick:(id)sender {
double stepperValue = _likeStepper.value;
_likesLbl.text = [NSString stringWithFormat:#"%.f", stepperValue];
[self updateLikes];
[self updateHist];
}
- (void) updateLikes {
// update the question with the new "liked" score
NSInteger likesN = [_likesLbl.text integerValue];
Questn *qInfo = [[Questn alloc] initQwID:thisQstn askID:0 wCat:#"na" wSit:#"na" wAns1:#"na" wPts1:0 wAns2:#"na" wPts2:0 wAns3:#"na" wPts3:0 wAns4:#"na" wPts4:0 wJust:#"na" wLikes:likesN ];
NSMutableURLRequest *reqPost = [SimplePost urlencodedRequestWithURL:[NSURL URLWithString:kLikesURL] andDataDictionary:[qInfo toDictQ]];
(void) [[NSURLConnection alloc] initWithRequest:reqPost delegate:self];
}
- (void) updateHist {
History *newH = [[History alloc] initHistID:thisUser hQid:thisQstn hPts:98989 hLiked:YES];
NSMutableURLRequest *reqHpost = [SimplePost urlencodedRequestWithURL:[NSURL URLWithString:kHistURL] andDataDictionary:[newH toDictH]];
(void) [[NSURLConnection alloc] initWithRequest:reqHpost delegate:self];
}
Messy, right? Here's my connection code:
// connection to URL finished with Plist-formatted user data array returned from PHP
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSDictionary *array = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:0 errorDescription:nil];
BOOL keyLikeExists = [array objectForKey:#"likes"] != nil;
if( keyLikeExists ) {
_likesLbl.text = [array objectForKey:#"likes"];
}
}
- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"Connection did fail." );
}
It all does a good job, and then a couple of seconds later it crashes with that "unrecognized selector" error mentioned above, like there's still some URL activity happening. There shouldn't be.
Anybody seen this kind of thing before? Many thanks for any help!
Somewhere in your code there's a call to the method isEqualToString:. The thing that's being sent that message is a NSNumber object rather than a string. Either there's a logic problem concerning the object type or there's a memory problem where a string was over-released and its memory is being re-used to hold a number.
Without seeing the context for the call, it's hard to guess.
If you break on the exception, the stack trace should tell you where in the code it's failing.