I am trying to develop a realtime multiplayer game for IOS by using cocos2d by using the tutorial on http://www.raywenderlich.com/3325/how-to-make-a-simple-multiplayer-game-with-game-center-tutorial-part-22
Everything works fine including auto matching with a random player but inviting a friend doesn't work because other device cannot receive an invitation.
When I clicked on invite friends button and then selected a friend by using the standard game center interface, it says waiting (forever) and nothing happens. My friend cannot receive an invitation from game center (no notifications).
I can invite a friend by using nearby friends functionality (when this functionality is enabled on both devices) but no invitation notification when nearby friends is disabled.
I spent hours and hours for searching on Google, found similar cases but no solution.
Some early feedback about possible answers:
I use two devices (one iPhone and one iPad), no simulator
All settings on iTunes connect are fine including multiplayer settings
I validated that both devices are connected to sandbox by using different test accounts
I've already checked the notification settings for Game center on both devices
I've already checked all proxy/firewall issues and tried on both WiFi and Cellular for both devices
Game invitations are enabled for both of the devices/accounts
I've already checked the bundle IDs, app version IDs, etc...
Both of the devices are IOS 6.x and App target version os IOS 5.0
I have no other issues about game center (leaderboards, random matchmaking, etc... all fine)
I call the inviteHandler method as soon after I authenticated a user as possible as mentioned in Apple documentation.
Here is my Game center helper class Header file:
#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>
#protocol GCHelperDelegate
- (void)matchStarted;
- (void)matchEnded;
- (void)match:(GKMatch *)match didReceiveData:(NSData *)data
fromPlayer:(NSString *)playerID;
- (void)inviteReceived;
#end
#interface GCHelper : NSObject <GKMatchmakerViewControllerDelegate, GKMatchDelegate>{
BOOL gameCenterAvailable;
BOOL userAuthenticated;
UIViewController *presentingViewController;
GKMatch *match;
BOOL matchStarted;
id <GCHelperDelegate> delegate;
NSMutableDictionary *playersDict;
GKInvite *pendingInvite;
NSArray *pendingPlayersToInvite;
NSMutableArray *unsentScores;
}
#property (retain) GKInvite *pendingInvite;
#property (retain) NSArray *pendingPlayersToInvite;
#property (assign, readonly) BOOL gameCenterAvailable;
#property (retain) NSMutableDictionary *playersDict;
#property (retain) UIViewController *presentingViewController;
#property (retain) GKMatch *match;
#property (assign) id <GCHelperDelegate> delegate;
- (void)findMatchWithMinPlayers:(int)minPlayers maxPlayers:(int)maxPlayers
viewController:(UIViewController *)viewController
delegate:(id<GCHelperDelegate>)theDelegate;
- (BOOL) reportAchievementIdentifier: (NSString*) identifier percentComplete: (float) percent;
+ (GCHelper *)sharedInstance;
- (void)authenticateLocalUser;
#end
And here is the implementation of the game center helper class
#import "GCHelper.h"
#implementation GCHelper
#synthesize gameCenterAvailable;
#synthesize presentingViewController;
#synthesize match;
#synthesize delegate;
#synthesize playersDict;
#synthesize pendingInvite;
#synthesize pendingPlayersToInvite;
#pragma mark Initialization
static GCHelper *sharedHelper = nil;
+ (GCHelper *) sharedInstance {
if (!sharedHelper) {
sharedHelper = [[GCHelper alloc] init];
}
return sharedHelper;
}
- (BOOL)isGameCenterAvailable {
// check for presence of GKLocalPlayer API
Class gcClass = (NSClassFromString(#"GKLocalPlayer"));
// check if the device is running iOS 4.1 or later
NSString *reqSysVer = #"4.1";
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
BOOL osVersionSupported = ([currSysVer compare:reqSysVer
options:NSNumericSearch] != NSOrderedAscending);
return (gcClass && osVersionSupported);
}
- (id)init {
if ((self = [super init])) {
gameCenterAvailable = [self isGameCenterAvailable];
if (gameCenterAvailable) {
NSNotificationCenter *nc =
[NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(authenticationChanged)
name:GKPlayerAuthenticationDidChangeNotificationName
object:nil];
}
}
return self;
}
- (void)authenticationChanged {
if ([GKLocalPlayer localPlayer].isAuthenticated && !userAuthenticated) {
NSLog(#"Authentication changed: player authenticated.");
userAuthenticated = TRUE;
[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
NSLog(#"Received invite");
self.pendingInvite = acceptedInvite;
self.pendingPlayersToInvite = playersToInvite;
[delegate inviteReceived];
};
} else if (![GKLocalPlayer localPlayer].isAuthenticated && userAuthenticated) {
NSLog(#"Authentication changed: player not authenticated");
userAuthenticated = FALSE;
}
}
- (void)lookupPlayers {
NSLog(#"Looking up %d players...", match.playerIDs.count);
[GKPlayer loadPlayersForIdentifiers:match.playerIDs withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
NSLog(#"Error retrieving player info: %#", error.localizedDescription);
matchStarted = NO;
[delegate matchEnded];
} else {
// Populate players dict
self.playersDict = [NSMutableDictionary dictionaryWithCapacity:players.count];
for (GKPlayer *player in players) {
NSLog(#"Found player: %#", player.alias);
[playersDict setObject:player forKey:player.playerID];
}
// Notify delegate match can begin
matchStarted = YES;
[delegate matchStarted];
}
}];
}
#pragma mark User functions
- (void)authenticateLocalUser {
if (!gameCenterAvailable) return;
NSLog(#"Authenticating local user...");
if ([GKLocalPlayer localPlayer].authenticated == NO) {
[[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:nil];
} else {
NSLog(#"Already authenticated!");
}
}
- (void)findMatchWithMinPlayers:(int)minPlayers maxPlayers:(int)maxPlayers viewController:(UIViewController *)viewController delegate:(id<GCHelperDelegate>)theDelegate {
if (!gameCenterAvailable) return;
matchStarted = NO;
self.match = nil;
self.presentingViewController = viewController;
delegate = theDelegate;
if (pendingInvite != nil) {
[presentingViewController dismissModalViewControllerAnimated:NO];
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithInvite:pendingInvite] autorelease];
mmvc.matchmakerDelegate = self;
[presentingViewController presentModalViewController:mmvc animated:YES];
self.pendingInvite = nil;
self.pendingPlayersToInvite = nil;
} else {
[presentingViewController dismissModalViewControllerAnimated:NO];
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = minPlayers;
request.maxPlayers = maxPlayers;
request.playersToInvite = pendingPlayersToInvite;
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
mmvc.matchmakerDelegate = self;
[presentingViewController presentModalViewController:mmvc animated:YES];
self.pendingInvite = nil;
self.pendingPlayersToInvite = nil;
}
}
#pragma mark GKMatchmakerViewControllerDelegate
// The user has cancelled matchmaking
- (void)matchmakerViewControllerWasCancelled:(GKMatchmakerViewController *)viewController {
[presentingViewController dismissModalViewControllerAnimated:YES];
}
// Matchmaking has failed with an error
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFailWithError:(NSError *)error {
[presentingViewController dismissModalViewControllerAnimated:YES];
NSLog(#"Error finding match: %#", error.localizedDescription);
}
// A peer-to-peer match has been found, the game should start
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)theMatch {
[presentingViewController dismissModalViewControllerAnimated:YES];
self.match = theMatch;
match.delegate = self;
if (!matchStarted && match.expectedPlayerCount == 0) {
NSLog(#"Ready to start match!");
[self lookupPlayers];
}
}
#pragma mark GKMatchDelegate
// The match received data sent from the player.
- (void)match:(GKMatch *)theMatch didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {
if (match != theMatch) return;
[delegate match:theMatch didReceiveData:data fromPlayer:playerID];
}
// The player state changed (eg. connected or disconnected)
- (void)match:(GKMatch *)theMatch player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
if (match != theMatch) return;
switch (state) {
case GKPlayerStateConnected:
// handle a new player connection.
NSLog(#"Player connected!");
if (!matchStarted && theMatch.expectedPlayerCount == 0) {
NSLog(#"Ready to start match!");
[self lookupPlayers];
}
break;
case GKPlayerStateDisconnected:
// a player just disconnected.
NSLog(#"Player disconnected!");
matchStarted = NO;
[delegate matchEnded];
break;
}
}
// The match was unable to connect with the player due to an error.
- (void)match:(GKMatch *)theMatch connectionWithPlayerFailed:(NSString *)playerID withError:(NSError *)error {
if (match != theMatch) return;
NSLog(#"Failed to connect to player with error: %#", error.localizedDescription);
matchStarted = NO;
[delegate matchEnded];
}
// The match was unable to be established with any players due to an error.
- (void)match:(GKMatch *)theMatch didFailWithError:(NSError *)error {
if (match != theMatch) return;
NSLog(#"Match failed with error: %#", error.localizedDescription);
matchStarted = NO;
[delegate matchEnded];
}
- (void)reportScore:(int64_t)score forCategory:(NSString *)category {
// Only execute if OS supports Game Center & player is logged in
if ([self isGameCenterAvailable] && [GKLocalPlayer localPlayer].authenticated == YES)
{
// Create score object
GKScore *scoreReporter = [[[GKScore alloc] initWithCategory:category] autorelease];
// Set the score value
scoreReporter.value = score;
// Try to send
[scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {
if (error != nil)
{
// Handle reporting error here by adding object to a serializable array, to be sent again later
[unsentScores addObject:scoreReporter];
}
}];
}
}
- (BOOL) reportAchievementIdentifier: (NSString*) identifier percentComplete: (float) percent {
if ([self isGameCenterAvailable] && [GKLocalPlayer localPlayer].authenticated == YES)
{
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).
}
}];
}
return YES;
}
return NO;
}
#end
And Finally this is how I call the game center from my game layer (I tried two different options but none of them worked)
Option 1
[[GCHelper sharedInstance] findMatchWithMinPlayers:2 maxPlayers:2 viewController: [[[UIApplication sharedApplication] keyWindow] rootViewController] delegate: self];
Option 2
AppController *app = (AppController*) [[UIApplication sharedApplication] delegate];
UINavigationController *viewController = [app navController];
[[GCHelper sharedInstance] findMatchWithMinPlayers:2 maxPlayers:2 viewController:viewController delegate:self];
Any help will be appreciated. Thanks in advance...
I've been dealing with multiplayer for a few months now and invitations have been a real issue for me....and like you, I used Ray's tutorial to get me started. I realized today that Ray's code has a bug in it where invites will not work if both clients have the GKMatchmakerView up. You need to dismiss it when you first receive the invite with something along the lines of:
[gcdelegate.viewController dismissViewControllerAnimated:YES
completion:^{
GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc] initWithInvite:pendingInvite];
mmvc.matchmakerDelegate = self;
[gcdelegate.viewController presentModalViewController:mmvc animated:YES];
self.pendingInvite = nil;
self.pendingPlayersToInvite = nil;
boo_invite=true;
}];
Ok, it seems that it again works without any changes in our code or network settings. I opened ticket to Apple support, bug records, etc... and it seems that some of them worked...
Now we understand that it was a bug in Game Center sandbox. As far as I see, sanbox version of game center is not that stable and people in Apple don't give enough attention to this service. There is also no way to check the system status on the internet.
I'm still continuing to discuss with Apple support in order to understand the reason and will share the all conversation here when it's completed.
Happy coding...
Related
#import "AppDelegate.h"
#import <Intents/Intents.h>
#import <CallKit/CallKit.h>
#import <PushKit/PushKit.h>
#pragma mark - PKPushRegistryDelegate
- (void)pushRegistry:(nonnull PKPushRegistry *)registry didUpdatePushCredentials:(nonnull PKPushCredentials *)pushCredentials forType:(nonnull PKPushType)type {
if ([pushCredentials.token length] == 0) {
NSLog(#"VoIP token NULL..");
}
}
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type
{
NSString *uuidString = payload.dictionaryPayload[#"UUID"];
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
NSString *phoneNumber = payload.dictionaryPayload[#"PhoneNumber"];
CallViewController *viewController = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"CallViewController"];
viewController.number = phoneNumber;
viewController.incoming = YES;
viewController.callId = uuid;
UIViewController *mainViewController = self.window.rootViewController;
[mainViewController presentViewController:viewController animated:YES completion:nil];
}
#import "CallManager.h"
#import <CallKit/CallKit.h>
#import <CallKit/CXError.h>
#interface CallManager () <CXProviderDelegate>
#property (nonatomic, strong) CXProvider *provider;
#property (nonatomic, strong) CXCallController *callController;
#property (nonatomic, strong) NSUUID *currentCall;
#end
#implementation CallManager
+ (CallManager*)shaaredInstance {
static CallManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[CallManager alloc] init];
[sharedInstance provider];
});
return sharedInstance;
}
- (void)reportIncomingCallForUUID:(NSUUID*)uuid phoneNumber:(NSString*)phoneNumber {
CXCallUpdate *update = [[CXCallUpdate alloc] init];
update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:phoneNumber];
__weak CallManager *weakSelf = self;
[self.provider reportNewIncomingCallWithUUID:uuid update:update completion:^(NSError * _Nullable error) {
if (!error) {
weakSelf.currentCall = uuid;
} else {
if (self.delegate && [self.delegate respondsToSelector:#selector(callDidFail)]) {
[self.delegate callDidFail];
}
}
}];
}
- (void)startCallWithPhoneNumber:(NSString*)phoneNumber {
CXHandle *handle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:phoneNumber];
self.currentCall = [NSUUID new];
CXStartCallAction *startCallAction = [[CXStartCallAction alloc] initWithCallUUID:self.currentCall handle:handle];
CXTransaction *transaction = [[CXTransaction alloc] init];
[transaction addAction:startCallAction];
[self requestTransaction:transaction];
}
- (void)endCall {
CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:self.currentCall];
CXTransaction *transaction = [[CXTransaction alloc] init];
[transaction addAction:endCallAction];
[self requestTransaction:transaction];
}
- (void)holdCall:(BOOL)hold {
CXSetHeldCallAction *holdCallAction = [[CXSetHeldCallAction alloc] initWithCallUUID:self.currentCall onHold:hold];
CXTransaction *transaction = [[CXTransaction alloc] init];
[transaction addAction:holdCallAction];
[self requestTransaction:transaction];
}
- (void)requestTransaction:(CXTransaction*)transaction {
[self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) {
if (error) {
NSLog(#"%#", error.localizedDescription);
if (self.delegate && [self.delegate respondsToSelector:#selector(callDidFail)]) {
[self.delegate callDidFail];
}
}
}];
}
#pragma mark - Getters
- (CXProvider*)provider {
if (!_provider) {
CXProviderConfiguration *configuration = [[CXProviderConfiguration alloc] initWithLocalizedName:#"IP-PBX"];
configuration.supportsVideo = YES;
configuration.maximumCallsPerCallGroup = 1;
configuration.supportedHandleTypes = [NSSet setWithObject:#(CXHandleTypePhoneNumber)];
_provider = [[CXProvider alloc] initWithConfiguration:configuration];
[_provider setDelegate:self queue:nil];
}
return _provider;
}
- (CXCallController*)callController {
if (!_callController) {
_callController = [[CXCallController alloc] init];
}
return _callController;
}
#pragma mark - CXProviderDelegate
- (void)providerDidReset:(CXProvider *)provider {
}
// Called when the provider has been fully created and is ready to send actions and receive updates
- (void)providerDidBegin:(CXProvider *)provider {
}
// If provider:executeTransaction:error: returned NO, each perform*CallAction method is called sequentially for each action in the transaction
- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action {
//todo: configure audio session
//todo: start network call
[self.provider reportOutgoingCallWithUUID:action.callUUID startedConnectingAtDate:nil];
[self.provider reportOutgoingCallWithUUID:action.callUUID connectedAtDate:nil];
if (self.delegate && [self.delegate respondsToSelector:#selector(callDidAnswer)]) {
[self.delegate callDidAnswer];
}
[action fulfill];
}
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
//todo: configure audio session
//todo: answer network call
if (self.delegate && [self.delegate respondsToSelector:#selector(callDidAnswer)]) {
[self.delegate callDidAnswer];
}
[action fulfill];
}
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
//todo: stop audio
//todo: end network call
self.currentCall = nil;
if (self.delegate && [self.delegate respondsToSelector:#selector(callDidEnd)]) {
[self.delegate callDidEnd];
}
[action fulfill];
}
- (void)provider:(CXProvider *)provider performSetHeldCallAction:(CXSetHeldCallAction *)action {
if (action.isOnHold) {
//todo: stop audio
} else {
//todo: start audio
}
if (self.delegate && [self.delegate respondsToSelector:#selector(callDidHold:)]) {
[self.delegate callDidHold:action.isOnHold];
}
[action fulfill];
}
I am using Call Kit in VOIP application (using ABTO VoIP SDK too). But I want to merge my VoiP App call history to my iPHONE recent tab. I added Call kit in my project everything was fine I am getting VoIP app call history to my iPhone recent tab. But I am facing issues while calling once I do incoming or outgoing calls I am getting callkit default screen which lead the incoming call on hold and outgoing to. Would there any way to manage callkit default screen? Would it be possible to manage two SDK at the same time??? I am using pushkit too..
Please help me to resolve this issue????
Whenever I am getting incoming call I am facing 2 calling screen one from Voip SDK and one from Call kit.. I want to manage that default screen
My App was rejected by Apple because it can't connect to other device running iOS 10.1.1 on Wi-Fi connected to an IPv6 network.
When I tap on connect, the app continues to search for invitees and no further user action is produced.
I use Multi-peer Connectivity and I never tested my App being connected to an IPv6(It's my first release). But the App run very fine without having any connection or being connected to IPv4 network.
I don't know why the App is running and connecting fine using the IPv4 and doesn't connect to peer if the iPad is connected to an IPv6 network.
So my Question: Is it possible to use Multi-peer Connectivity with IPv6 so that Apple can approve the App or how should I handle this Issue ?
Here is my Code, maybe it is something wrong there.
#interface ConnectionManageriOS7 () <MCSessionDelegate, MCBrowserViewControllerDelegate>
{
UILocalNotification *_expireNotification;
UIBackgroundTaskIdentifier _taskId;
}
#property (nonatomic, strong) MCSession *session;
#property (nonatomic, strong) MCPeerID *localPeerID;
#property (nonatomic, strong) MCBrowserViewController *browserVC;
#property (nonatomic, strong) MCAdvertiserAssistant *advertiser;
#end
static ConnectionManageriOS7 *_manager = nil;
#implementation ConnectionManageriOS7
+ (ConnectionManageriOS7 *)connectManager {
#synchronized([ConnectionManageriOS7 class]){
if (_manager == nil) {
_manager = [[ConnectionManageriOS7 alloc] init];
}
return _manager;
}
return nil;
}
- (id)init {
self = [super init];
if (self) {
[self setupSessionAndAdvertiser];
}
return self;
}
- (void)setupSessionAndAdvertiser {
_localPeerID = [[MCPeerID alloc] initWithDisplayName:[UIDevice currentDevice].name];;
_session = [[MCSession alloc] initWithPeer:_localPeerID];
_session.delegate = self;
}
- (void)connectWithDelegate:(id)delegate {
_delegate = delegate;
if (_session.connectedPeers.count) {
if ([_delegate respondsToSelector:#selector(didConntectedWithManager:)]) {
[_delegate didConntectedWithManager:self];
}
} else {
if (_advertiser == nil) {
_advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType:VISUS_Service
discoveryInfo:nil
session:_session];
_isConnected = NO;
[_advertiser start];
}
if (_browserVC == nil) {
_browserVC = [[MCBrowserViewController alloc] initWithServiceType:VISUS_Service session:_session];
_browserVC.delegate = self;
}
[(UIViewController *)delegate presentViewController:_browserVC
animated:YES completion:nil];
}
}
- (void)sendMessage:(NSString *)message {
NSData *textData = [message dataUsingEncoding:NSASCIIStringEncoding];
NSLog(#"Send Data: %#", message);
NSError *error = nil;
[_session sendData:textData
toPeers:_session.connectedPeers
withMode:MCSessionSendDataReliable
error:&error];
if (error) {
//
[self session:_session peer:nil didChangeState:MCSessionStateNotConnected];
NSLog(#"error %#", error.userInfo);
}
}
- (void)stopService {
NSLog(#"Stop Service");
[_advertiser stop];
_advertiser = nil;
_browserVC = nil;
}
#pragma marks -
#pragma marks MCBrowserViewControllerDelegate
- (void) dismissBrowserVC{
[_browserVC dismissViewControllerAnimated:YES completion:nil];
}
// Notifies the delegate, when the user taps the done button
- (void)browserViewControllerDidFinish:(MCBrowserViewController *)browserViewController {
if ([_delegate respondsToSelector:#selector(didConntectedWithManager:)]) {
[_delegate didConntectedWithManager:self];
}
[self dismissBrowserVC];
}
// Notifies delegate that the user taps the cancel button.
- (void)browserViewControllerWasCancelled:(MCBrowserViewController *)browserViewController{
if (_browserVC == nil) {
[browserViewController dismissViewControllerAnimated:YES completion:nil];
}else {
[self dismissBrowserVC];
}
}
#pragma marks -
#pragma marks MCBrowserViewControllerDelegate
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
if (state != MCSessionStateConnecting) {
if (state == MCSessionStateConnected) {
_isConnected = true;
if ([_delegate respondsToSelector:#selector(willConntectedWithManager:)]) {
[_delegate willConntectedWithManager:self];
}
}
else {
_isConnected = false;
[self stopService];
if ([_delegate respondsToSelector:#selector(didDisconntectedWithManager:)]) {
[_delegate didDisconntectedWithManager:self];
}
}
}
}
// Received data from remote peer
- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
// Decode data back to NSString
NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Receive Data: %#", message);
// append message to text box:
dispatch_async(dispatch_get_main_queue(), ^{
if ([_delegate respondsToSelector:#selector(connectionManager:receivedString:)]) {
[_delegate connectionManager:self receivedString:message];
}
});
}
- (void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error {
_isConnected = false;
[self stopService];
NSLog(#"----- Error ----- %#", error.localizedDescription);
}
// Received a byte stream from remote peer
- (void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID {
}
// Start receiving a resource from remote peer
- (void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress {
}
- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
{
certificateHandler(YES);
}
- (void) createExpireNotification
{
[self killExpireNotification];
if (_session.connectedPeers.count != 0) // if peers connected, setup kill switch
{
NSTimeInterval gracePeriod = 20.0f;
// create notification that will get the user back into the app when the background process time is about to expire
NSTimeInterval msgTime = UIApplication.sharedApplication.backgroundTimeRemaining - gracePeriod;
UILocalNotification* n = [[UILocalNotification alloc] init];
_expireNotification = n;
_expireNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:msgTime];
_expireNotification.alertBody = #"Bluetooth Connectivity is about to disconnect. Open the app to resume Test";
_expireNotification.soundName = UILocalNotificationDefaultSoundName;
_expireNotification.applicationIconBadgeNumber = 1;
[UIApplication.sharedApplication scheduleLocalNotification:_expireNotification];
}
}
- (void) killExpireNotification
{
if (_expireNotification != nil)
{
[UIApplication.sharedApplication cancelLocalNotification:_expireNotification];
_expireNotification = nil;
}
}
- (void)bacgroundHandling {
_taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
{
[self stopService];
[[UIApplication sharedApplication] endBackgroundTask:_taskId];
_taskId = UIBackgroundTaskInvalid;
}];
[self createExpireNotification];
}
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler
{
// http://down.vcnc.co.kr/WWDC_2013/Video/708.pdf -- wwdc tutorial, this part is towards the end (p119)
// self.arrayInvitationHandler = [NSArray arrayWithObject:[invitationHandler copy]];
// ask the user
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:peerID.displayName
message:#"Would like to create a session with you"
delegate:self
cancelButtonTitle:#"Decline" otherButtonTitles:#"Accept", nil];
[alertView show];
}
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// retrieve the invitationHandler and check whether the user accepted or declined the invitation...
BOOL accept = (buttonIndex != alertView.cancelButtonIndex) ? YES : NO;
// respond
if(accept) {
// void (^invitationHandler)(BOOL, MCSession *) = [self.arrayInvitationHandler objectAtIndex:0];
// invitationHandler(accept, self.mySession);
}
else
{
NSLog(#"Session disallowed");
}
}
- (void)terminate {
[self killExpireNotification];
[self stopService];
}
#end
I have solved the problem. For everybody with simular problem:
It wasn't a problem with IPv6, it is a matter of how to use Multipeer connectivity. In my Case I tryied to the the IPv6 connection with a iPad and a Simulator. And I used my Macbook for creating a nat64 network. And the reason why the simulator and iPad never saw each other the fact that they where not connected to same wifi network.
Solution:
Just take for testing two iPads and use your mac as nat64 network accesspoint.
I'm using a host/client approach to using MultiPeer Connectivity.
So, when a user his the disconnect button
-(IBAction)disconnect:(id)sender {
[_appDelegate.mcManager.session disconnect];
[_arrConnectedDevices removeAllObjects];
ConnectionsViewController *game = [self.storyboard instantiateViewControllerWithIdentifier:#"ConnectionsViewController"];
[self presentViewController:game animated:YES completion:nil];
}
Now, this works fine. From the hosts point of view it receives a disconnect message in the log. and the client moves to the new view controller. And the table is updated. with this.
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification{
MCPeerID *peerID = [[notification userInfo] objectForKey:#"peerID"];
NSString *peerDisplayName = peerID.displayName;
MCSessionState state = [[[notification userInfo] objectForKey:#"state"] intValue];
if (state != MCSessionStateConnecting) {
if (state == MCSessionStateConnected) {
if (_makeSureImHost) {
[_arrConnectedDevices addObject:peerDisplayName];
[_tblConnectedDevices performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
}
else {
[self sendMessageToHostWithMessage:#"deviceInfo"];
}
}
else if (state == MCSessionStateNotConnected){
if ([_arrConnectedDevices count] > 0) {
int indexOfPeer = (int)[_arrConnectedDevices indexOfObject:peerDisplayName];
[_arrConnectedDevices removeObjectAtIndex:indexOfPeer];
NSLog(#"%# Disconnected", peerDisplayName);
[_tblConnectedDevices performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
_tblConnectedDevices.frame = CGRectMake(_backgroundImage.frame.size.width / 2 - 150, self.backgroundImage.frame.size.height / 3, 300, 150);
}
}
}
}
END OF LOBBY VIEW CONTROLLER
START OF CONNECTION VIEW CONTROLLER
When a client presses to browse for local devices this runs
- (IBAction)browseForDevices:(id)sender {
[UIView animateWithDuration:0.5f
animations:^{
_searchButton.frame = CGRectMake(-100, self.backgroundImage.frame.size.height/2 + 60, 100, 35.0);
_hostButton.alpha = 0;
_modeLabel.alpha = 0;
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[_appDelegate mcManager] setupPeerAndSessionWithDisplayName:[UIDevice currentDevice].name];
[[_appDelegate mcManager] advertiseSelf:false];
[[_appDelegate mcManager] setupMCBrowser];
[[[_appDelegate mcManager] browser] setDelegate:self];
_appDelegate.mcManager.browser.maximumNumberOfPeers = 1;
_appDelegate.mcManager.browser.modalTransitionStyle=UIModalTransitionStyleCrossDissolve;
[self presentViewController:[[_appDelegate mcManager] browser] animated:YES completion:nil];
});
}
When a connection is established
-(void)browserViewControllerDidFinish:(MCBrowserViewController *)browserViewController {
[_appDelegate.mcManager.browser dismissViewControllerAnimated:NO completion:^{
[self launchViewController];
}];
}
-(void)launchViewController {
LobbyViewController *lobby = [self.storyboard instantiateViewControllerWithIdentifier:#"LobbyViewController"];
[self presentViewController:lobby animated:NO completion:nil];
}
From this
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification {
MCPeerID *peerID = [[notification userInfo] objectForKey:#"peerID"];
NSString *peerDisplayName = peerID.displayName;
MCSessionState state = [[[notification userInfo] objectForKey:#"state"] intValue];
if (state != MCSessionStateConnecting) {
if (state == MCSessionStateConnected) {
[self browserViewControllerDidFinish:[[_appDelegate mcManager] browser]];
}
}
else if (state == MCSessionStateNotConnected){
if ([_arrConnectedDevices count] > 0) {
int indexOfPeer = (int)[_arrConnectedDevices indexOfObject:peerDisplayName];
[_arrConnectedDevices removeObjectAtIndex:indexOfPeer];
}
}
}
Now. When a connection is made for the first time. This all works flawlessly. It connects, loads the view, host starts the game, game works fine and data is transferred perfectly.
However, if you disconnect from the lobby. Get moved to the connection viewcontroller then browse for devices again. It will connect, however the lobby viewcontroller will NOT be in the view hierarchy and will close the browser and stay in the connection view controller.
Then, to top it off, the connection has been made. yet, when it receives a message from the host, it will send the response, twice.. or three times, or four, leading me to a dead end. The only thing I can presume is that the previous session is being remembered somehow from the "clients" point of view.
Now, steps I can take to avoid this mess. If I kill the app and relaunch it I can now connect again from the clients point of view. Which leads me to believe, the problem is on the clients end.
My problem is that I have to absolutely sort this out. So a disconnect will fully remove everything from the session. So they can reconnect again. And cannot rely on a message to tell the user to restart their application. It just cannot be.
Here's my entire MCManager.m file.
#implementation MCManager
-(id)init{
self = [super init];
if (self) {
_peerID = nil;
_session = nil;
_browser = nil;
_advertiser = nil;
}
return self;
}
-(void)setupPeerAndSessionWithDisplayName:(NSString *)displayName{
_peerID = [[MCPeerID alloc] initWithDisplayName:displayName];
_session = [[MCSession alloc] initWithPeer:_peerID];
_session.delegate = self;
}
-(void)setupMCBrowser{
_browser = [[MCBrowserViewController alloc] initWithServiceType:#"chat-files" session:_session];
}
-(void)advertiseSelf:(BOOL)shouldAdvertise{
if (shouldAdvertise) {
_advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType:#"chat-files"
discoveryInfo:nil
session:_session];
[_advertiser start];
}
else{
[_advertiser stop];
_advertiser = nil;
}
}
-(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{
NSDictionary *dict = #{#"peerID": peerID,
#"state" : [NSNumber numberWithInt:state]
};
[[NSNotificationCenter defaultCenter] postNotificationName:#"MCDidChangeStateNotification"
object:nil
userInfo:dict];
}
-(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
NSDictionary *dict = #{#"data": data,
#"peerID": peerID
};
[[NSNotificationCenter defaultCenter] postNotificationName:#"MCDidReceiveDataNotification"
object:nil
userInfo:dict];
}
-(void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress{
}
-(void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error{
}
-(void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID{
}
#end
#import <MultipeerConnectivity/MultipeerConnectivity.h>
#interface MCManager : NSObject <MCSessionDelegate>
#property (nonatomic, strong) MCPeerID *peerID;
#property (nonatomic, strong) MCSession *session;
#property (nonatomic, strong) MCBrowserViewController *browser;
#property (nonatomic, strong) MCAdvertiserAssistant *advertiser;
-(void)setupPeerAndSessionWithDisplayName:(NSString *)displayName;
-(void)setupMCBrowser;
-(void)advertiseSelf:(BOOL)shouldAdvertise;
#end
If anyone knows what I'm doing wrong I'd much appreciate it. This is driving me nuts.
[[NSNotificationCenter defaultCenter] removeObserver:name:object:];
Has fixed all my problems. Hopefully helps some other people too.
With Swift, I end the multipeer session as follows (after completing checking the deinit of session tracker):
func stopSession() {
self.serviceBrowser.stopBrowsingForPeers()
self.serviceBrowser.delegate = nil
self.serviceAdvertiser.stopAdvertisingPeer()
self.serviceAdvertiser.delegate = nil
self.session.disconnect()
self.peerSessionIDs.removeAll()
self.session.delegate = nil
self.session = nil
self.multipeerConnectivityService = nil
self.serviceType = nil
}
In other words, everything that was registered and the initializer is de-initialized. I am doing this in reverse order, but I'm not sure if order is important here.
In GCHelper.h
#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>
#interface abGCHelper : NSObject {
BOOL gameCenterAvailable;
BOOL userAuthenticated;
}
#property (assign, readonly) BOOL gameCenterAvailable;
-(void)reportScore:(int64_t)score forLeaderboardID:(NSString*)identifier;
#end
In GCHelper.m
#import "abGCHelper.h"
#implementation abGCHelper
#synthesize gameCenterAvailable;
static abGCHelper *_sharedHelper = nil;
-(abGCHelper*)defaultHelper {
// dispatch_once will ensure that the method is only called once (thread-safe)
static dispatch_once_t pred = 0;
dispatch_once(&pred, ^{
_sharedHelper = [[abGCHelper alloc] init];
});
return _sharedHelper;
}
- (BOOL)isGameCenterAvailable {
// check for presence of GKLocalPlayer API
Class gcClass = (NSClassFromString(#"GKLocalPlayer"));
// check if the device is running iOS 4.1 or later
NSString *reqSysVer = #"4.1";
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
BOOL osVersionSupported = ([currSysVer compare:reqSysVer
options:NSNumericSearch] != NSOrderedAscending);
return (gcClass && osVersionSupported);
}
- (id)init {
if ((self = [super init])) {
gameCenterAvailable = [self isGameCenterAvailable];
if (gameCenterAvailable) {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(authenticationChanged) name:GKPlayerAuthenticationDidChangeNotificationName object:nil];
}
}
return self;
}
- (void)authenticateLocalUserOnViewController:(UIViewController*)viewController
setCallbackObject:(id)obj
withPauseSelector:(SEL)selector
{
if (!gameCenterAvailable) return;
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
NSLog(#"Authenticating local user...");
if (localPlayer.authenticated == NO) {
[localPlayer setAuthenticateHandler:^(UIViewController* authViewController, NSError *error) {
if (authViewController != nil) {
if (obj) {
[obj performSelector:selector withObject:nil afterDelay:0];
}
[viewController presentViewController:authViewController animated:YES completion:^ {
}];
} else if (error != nil) {
// process error
}
}];
}
else {
NSLog(#"Already authenticated!");
}
}
-(void)authenticationChanged {
if ([GKLocalPlayer localPlayer].isAuthenticated && !userAuthenticated) {
NSLog(#"Authentication changed: player authenticated.");
userAuthenticated = TRUE;
// Load the leaderboard info
// Load the achievements
} else if (![GKLocalPlayer localPlayer].isAuthenticated && userAuthenticated) {
NSLog(#"Authentication changed: player not authenticated.");
userAuthenticated = FALSE;
}
}
-(void)reportScore:(int64_t)score forLeaderboardID:(NSString*)identifier
{
GKScore *scoreReporter = [[GKScore alloc] initWithLeaderboardIdentifier: identifier];
scoreReporter.value = score;
scoreReporter.context = 0;
[GKScore reportScores:#[scoreReporter] withCompletionHandler:^(NSError *error) {
if (error == nil) {
NSLog(#"Score reported successfully!");
} else {
NSLog(#"Unable to report score!");
}
}];
}
I referred to online tutorials to do this. Now, the problem is:
How do I submit the highScore defined in MyScene.m ?
Where do I have to put my Leaderboard ID?
Please help
Thanks...
I'm following the tutorial at:
http://mobile.tutsplus.com/tutorials/iphone/building-a-jabber-client-for-ios-server-setup/ to set up an iOS app with an ejabberd server. So far I have pretty much copied the code over to a new project.
My problem is that the XMPP delegate functions AppDelegate.m are not being called when run on the phone. Everything works fine in Simulator and the two functions below are called.
- (void)xmppStreamDidConnect:(XMPPStream *)sender {
NSLog(#"in WSAppDelegate - xmppStreamDidConnect");
isOpen = YES;
NSError *error = nil;
[[self xmppStream] authenticateWithPassword:password error:&error];
}
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {
NSLog(#"in WSAppDelegate - xmppStreamDidAuthenticate");
[self goOnline];
}
I am able to connect on both the phone and simulator as this call runs without error:
[xmppStream connect:&error]
Here is my AppDelegate.h code:
#import <UIKit/UIKit.h>
#import "XMPPRoster.h"
#import "XMPP.h"
#import "SMChatDelegate.h"
#import "SMMessageDelegate.h"
#class SMBuddyListViewController;
#interface WSAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
SMBuddyListViewController *viewController;
XMPPStream *xmppStream;
XMPPRoster *xmppRoster;
NSString *password;
BOOL isOpen;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet SMBuddyListViewController *viewController;
#property (nonatomic, readonly) XMPPStream *xmppStream;
#property (nonatomic, readonly) XMPPRoster *xmppRoster;
#property (nonatomic, assign) id _chatDelegate;
#property (nonatomic, assign) id _messageDelegate;
- (BOOL)connect;
- (void)disconnect;
#end
And AppDelegate.m:
#import "WSBuddyListViewController.h"
#interface WSAppDelegate()
- (void)setupStream;
- (void)goOnline;
- (void)goOffline;
#end
#implementation WSAppDelegate
#synthesize xmppStream;
#synthesize xmppRoster;
#synthesize window;
#synthesize viewController;
#synthesize _chatDelegate;
#synthesize _messageDelegate;
- (void)applicationWillResignActive:(UIApplication *)application {
[self disconnect];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
[self setupStream];
BOOL connected = NO;
connected = [self connect];
NSLog(#"*** connected = %i", connected);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}
- (void)setupStream {
NSLog(#"in WSAppDelegate - setupStream");
xmppStream = [[XMPPStream alloc] init];
[xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
[xmppStream setHostName:#"localhost"];
}
- (void)goOnline {
NSLog(#"in WSAppDelegate - goOnline");
XMPPPresence *presence = [XMPPPresence presence];
[[self xmppStream] sendElement:presence];
}
- (void)goOffline {
XMPPPresence *presence = [XMPPPresence presenceWithType:#"unavailable"];
[[self xmppStream] sendElement:presence];
}
- (BOOL)connect {
NSLog(#"in WSAppDelegate - connect");
[self setupStream];
NSString *jabberID = [[NSUserDefaults standardUserDefaults] stringForKey:#"userID"];
NSString *myPassword = [[NSUserDefaults standardUserDefaults] stringForKey:#"userPassword"];
if (![xmppStream isDisconnected]) {
NSLog(#"in WSAppDelegate - connect - if (![xmppStream isDisconnected]) ");
return YES;
}
if (jabberID == nil || myPassword == nil) {
NSLog(#"in WSAppDelegate - connect - if (jabberID == nil || myPassword == nil)");
return NO;
}
[xmppStream setMyJID:[XMPPJID jidWithString:jabberID]];
password = myPassword;
NSError *error = nil;
if (![xmppStream connect:&error])
{
NSLog(#"in WSAppDelegate - connect - if (![xmppStream connect:&error]))");
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error"
message:[NSString stringWithFormat:#"Can't connect to server %#", [error localizedDescription]]
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
return NO;
}
return YES;
}
- (void)disconnect {
[self goOffline];
[xmppStream disconnect];
[_chatDelegate didDisconnect];
}
#pragma mark -
#pragma mark XMPP delegates
- (void)xmppStreamDidConnect:(XMPPStream *)sender {
NSLog(#"in WSAppDelegate - xmppStreamDidConnect");
isOpen = YES;
NSError *error = nil;
[[self xmppStream] authenticateWithPassword:password error:&error];
}
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {
NSLog(#"in WSAppDelegate - xmppStreamDidAuthenticate");
[self goOnline];
}
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq {
return NO;
}
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message {
NSLog(#"in WSAppDelegate - xmppStream:(XMPPStream *)sender didReceiveMessage");
NSString *msg = [[message elementForName:#"body"] stringValue];
NSString *from = [[message attributeForName:#"from"] stringValue];
NSMutableDictionary *m = [[NSMutableDictionary alloc] init];
[m setObject:msg forKey:#"msg"];
[m setObject:from forKey:#"sender"];
[_messageDelegate newMessageReceived:m];
}
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence {
NSLog(#"in WSAppDelegate - xmppStream:(XMPPStream *)sender didReceivePresence:");
NSString *presenceType = [presence type]; // online/offline
NSString *myUsername = [[sender myJID] user];
NSString *presenceFromUser = [[presence from] user];
if (![presenceFromUser isEqualToString:myUsername]) {
if ([presenceType isEqualToString:#"available"]) {
[_chatDelegate newBuddyOnline:[NSString stringWithFormat:#"%##%#", presenceFromUser, #"localhost"]];
} else if ([presenceType isEqualToString:#"unavailable"]) {
[_chatDelegate buddyWentOffline:[NSString stringWithFormat:#"%##%#", presenceFromUser, #"localhost"]];
}
}
}
- (void)dealloc {
[xmppStream removeDelegate:self];
[xmppRoster removeDelegate:self];
[xmppStream disconnect];
}
#end
If you look at your setupStream method, you are using the name "localhost." This is leading me to believe the server is on your development machine, and the device is trying to connect to itself (localhost). You will have to replace that with your server name.
This likely works in the simulator because the the client and server are one and the same.
UPDATE:
I just read through the tutorial, and it does not do a good job at all at describing how it would work on real device.
as Mike D said, you're connecting to the server which is on your machine([xmppStream setHostName:#"localhost"];)
In order to connect to the "localhost" you have to change hosts file on your device(/etc/hosts), but that is forbidden by Apple, since your app cant change stuff outside the sandbox.(unless the device is jailbroken).
I've done this stuff on an android phone, when faced similar problem in the past.
Check this discussion(Does hosts file exist on the iPhone? How to change it?)