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...
Related
I am working on Twilio programmable voice SDK. I integrated it with the help of documentation and GitHub SDK. I can make a call from iOS to any number. It works fine.
The issue is on receiving a call on Twilio number. I did everything that was mentioned in Twilio installation documentation and GitHub SDK, but its not working.
here is the code:
#import PushKit;
#import CallKit;
#import TwilioVoice;
#import UserNotifications;
static NSString * kAccessToken = #"";
NSString * phoneNumber = #"";
NSString * newToken = #"";
static NSString *const kTwimlParamTo = #"to";
static NSInteger const kRegistrationTTLInDays = 365;
NSString * const kCachedDeviceToken = #"CachedDeviceToken";
NSString * const kCachedBindingTime = #"CachedBindingTime";
#interface RCTCallPackageModule () <TVONotificationDelegate, TVOCallDelegate, CXProviderDelegate, UITextFieldDelegate, AVAudioPlayerDelegate , PushKitEventDelegate, PKPushRegistryDelegate>
#property (nonatomic, weak) id<PushKitEventDelegate> pushKitEventDelegate;
#property (nonatomic, strong) void(^incomingPushCompletionCallback)(void);
#property (nonatomic, strong) void(^callKitCompletionCallback)(BOOL);
#property (nonatomic, strong) TVODefaultAudioDevice *audioDevice;
#property (nonatomic, strong) NSMutableDictionary *activeCallInvites;
#property (nonatomic, strong) NSMutableDictionary *activeCalls;
// activeCall represents the last connected call
#property (nonatomic, strong) TVOCall *activeCall;
#property (nonatomic, strong) CXProvider *callKitProvider;
#property (nonatomic, strong) CXCallController *callKitCallController;
#property (nonatomic, assign) BOOL userInitiatedDisconnect;
#property (nonatomic, assign) BOOL playCustomRingback;
#property (nonatomic, strong) AVAudioPlayer *ringtonePlayer;
#end
//#import <React/RCTLog.h>
#implementation RCTCallPackageModule
-(NSString *)fetchAccessToken {
NSString *accessToken = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://54.172.240.200:4000/accessToken"]
encoding:NSUTF8StringEncoding
error:nil];
return accessToken;
}
-(void) mainIntializerFunction {
self.pushKitEventDelegate = self;
self.callKitCallController = [[CXCallController alloc] init];
// fetchData();
/* Please note that the designated initializer `[CXProviderConfiguration initWithLocalizedName:]` has been deprecated on iOS 14. */
CXProviderConfiguration *configuration = [[CXProviderConfiguration alloc] initWithLocalizedName:#"Voice Quickstart"];
configuration.maximumCallGroups = 1;
configuration.maximumCallsPerCallGroup = 1;
self.callKitProvider = [[CXProvider alloc] initWithConfiguration:configuration];
[self.callKitProvider setDelegate:self queue:nil];
self.audioDevice = [TVODefaultAudioDevice audioDevice];
TwilioVoiceSDK.audioDevice = self.audioDevice;
self.activeCallInvites = [NSMutableDictionary dictionary];
self.activeCalls = [NSMutableDictionary dictionary];
self.playCustomRingback = NO;
[self MainFunctionToCall]; }
- (void)dealloc {
if (self.callKitProvider) {
[self.callKitProvider invalidate];
} }
-(void) MainFunctionToCall {
if (self.activeCall != nil) {
self.userInitiatedDisconnect = YES;
[self performEndCallActionWithUUID:self.activeCall.uuid];
} else {
NSUUID *uuid = [NSUUID UUID];
NSString *handle = #"Voice Bot";
[self checkRecordPermission:^(BOOL permissionGranted) {
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound)
completionHandler:^(BOOL granted, NSError * _Nullable error) {
// Enable or disable features based on authorization.
}];
// [[UIApplication sharedApplication] registerForRemoteNotifications];
[self performStartCallActionWithUUID:uuid handle:handle];
}];
}
}
// performEndCallActiveUUID
- (void)performEndCallActionWithUUID:(NSUUID *)uuid {
CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:uuid];
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:endCallAction];
[self.callKitCallController requestTransaction:transaction completion:^(NSError *error) {
if (error) {
NSLog(#"EndCallAction transaction request failed: %#", [error localizedDescription]);
}
else {
}
}];
}
// checkRecordPermission
- (void)checkRecordPermission:(void(^)(BOOL permissionGranted))completion {
AVAudioSessionRecordPermission permissionStatus = [[AVAudioSession sharedInstance] recordPermission];
switch (permissionStatus) {
case AVAudioSessionRecordPermissionGranted:
// Record permission already granted.
completion(YES);
break;
case AVAudioSessionRecordPermissionDenied:
// Record permission denied.
completion(NO);
break;
case AVAudioSessionRecordPermissionUndetermined:
{
[[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
completion(granted);
}];
break;
}
default:
completion(NO);
break;
}
}
#pragma mark - CallKit Actions
- (void)performStartCallActionWithUUID:(NSUUID *)uuid handle:(NSString *)handle {
if (uuid == nil || handle == nil) {
return;
}
CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
CXStartCallAction *startCallAction = [[CXStartCallAction alloc] initWithCallUUID:uuid handle:callHandle];
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:startCallAction];
[self.callKitCallController requestTransaction:transaction completion:^(NSError *error) {
if (error) {
NSLog(#"StartCallAction transaction request failed: %#", [error localizedDescription]);
} else {
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
callUpdate.remoteHandle = callHandle;
callUpdate.supportsDTMF = YES;
callUpdate.supportsHolding = YES;
callUpdate.supportsGrouping = NO;
callUpdate.supportsUngrouping = NO;
callUpdate.hasVideo = NO;
[self.callKitProvider reportCallWithUUID:uuid updated:callUpdate];
}
}];
}
#pragma mark - PushKitEventDelegate
- (void)credentialsUpdated:(PKPushCredentials *)credentials {
NSData *cachedDeviceToken = [[NSUserDefaults standardUserDefaults] objectForKey:kCachedDeviceToken];
if ([self registrationRequired] || ![cachedDeviceToken isEqualToData:credentials.token]) {
cachedDeviceToken = credentials.token;
[TwilioVoiceSDK registerWithAccessToken:kAccessToken
deviceToken:cachedDeviceToken
completion:^(NSError *error) {
if (error) {
} else {
// Save the device token after successfully registered.
[[NSUserDefaults standardUserDefaults] setObject:cachedDeviceToken forKey:kCachedDeviceToken];
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:kCachedBindingTime];
}
}];
}
}
- (BOOL)registrationRequired {
BOOL registrationRequired = YES;
NSDate *lastBindingCreated = [[NSUserDefaults standardUserDefaults] objectForKey:kCachedBindingTime];
if (lastBindingCreated) {
NSDateComponents *dayComponent = [[NSDateComponents alloc] init];
// Register upon half of the TTL
dayComponent.day = kRegistrationTTLInDays / 2;
NSDate *bindingExpirationDate = [[NSCalendar currentCalendar] dateByAddingComponents:dayComponent toDate:lastBindingCreated options:0];
NSDate *currentDate = [NSDate date];
if ([bindingExpirationDate compare:currentDate] == NSOrderedDescending) {
registrationRequired = NO;
}
}
return registrationRequired;
}
- (void)credentialsInvalidated {
NSData *cachedDeviceToken = [[NSUserDefaults standardUserDefaults] objectForKey:kCachedDeviceToken];
if ([cachedDeviceToken length] > 0) {
[TwilioVoiceSDK unregisterWithAccessToken:kAccessToken
deviceToken:cachedDeviceToken
completion:^(NSError *error) {
if (error) {
} else {
}
}];
}
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kCachedDeviceToken];
// Remove the cached binding as credentials are invalidated
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kCachedBindingTime];
}
-(void)incomingPushReceived:(PKPushPayload *)payload withCompletionHandler:(void (^)(void))completion {
// The Voice SDK will use main queue to invoke `cancelledCallInviteReceived:error` when delegate queue is not passed
if (![TwilioVoiceSDK handleNotification:payload.dictionaryPayload delegate:self delegateQueue:nil]) {
}
if (completion) {
if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion < 13) {
// Save for later when the notification is properly handled.
self.incomingPushCompletionCallback = completion;
} else {
completion();
}
}
}
- (void)incomingPushHandled {
if (self.incomingPushCompletionCallback) {
self.incomingPushCompletionCallback();
self.incomingPushCompletionCallback = nil;
}
}
#pragma mark - TVONotificationDelegate
- (void)callInviteReceived:(TVOCallInvite *)callInvite {
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:kCachedBindingTime];
if (callInvite.callerInfo.verified != nil && [callInvite.callerInfo.verified boolValue]) {
}
NSString *from = #"Voice Bot";
if (callInvite.from) {
from = [callInvite.from stringByReplacingOccurrencesOfString:#"client:" withString:#""];
}
// Always report to CallKit
[self reportIncomingCallFrom:from withUUID:callInvite.uuid];
self.activeCallInvites[[callInvite.uuid UUIDString]] = callInvite;
if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion < 13) {
[self incomingPushHandled];
}
}
- (void)cancelledCallInviteReceived:(TVOCancelledCallInvite *)cancelledCallInvite error:(NSError *)error {
TVOCallInvite *callInvite;
for (NSString *uuid in self.activeCallInvites) {
TVOCallInvite *activeCallInvite = [self.activeCallInvites objectForKey:uuid];
if ([cancelledCallInvite.callSid isEqualToString:activeCallInvite.callSid]) {
callInvite = activeCallInvite;
break;
}
}
if (callInvite) {
[self performEndCallActionWithUUID:callInvite.uuid];
[self.activeCallInvites removeObjectForKey:callInvite.uuid.UUIDString];
}
}
- (void)callDidStartRinging:(TVOCall *)call {
NSLog(#"callDidStartRinging:");
if (self.playCustomRingback) {
[self playRingback];
}
// [self.placeCallButton setTitle:#"Ringing" forState:UIControlStateNormal];
}
- (void)callDidConnect:(TVOCall *)call {
NSLog(#"callDidConnect:");
if (self.playCustomRingback) {
[self stopRingback];
}
[self sendEventWithName:#"onSessionConnect" body:#"Connected"];
self.callKitCompletionCallback(YES);
}
- (void)call:(TVOCall *)call isReconnectingWithError:(NSError *)error {
NSLog(#"Call is reconnecting");
}
- (void)callDidReconnect:(TVOCall *)call {
NSLog(#"Call reconnected");
}
- (void)call:(TVOCall *)call didFailToConnectWithError:(NSError *)error {
NSLog(#"Call failed to connect: %#", error);
self.callKitCompletionCallback(NO);
[self.callKitProvider reportCallWithUUID:call.uuid endedAtDate:[NSDate date] reason:CXCallEndedReasonFailed];
[self sendEventWithName:#"onSessionConnect" body:#"Failure"];
[self callDisconnected:call];
}
- (void)call:(TVOCall *)call didDisconnectWithError:(NSError *)error {
if (error) {
NSLog(#"Call failed: %#", error);
} else {
NSLog(#"Call disconnected");
}
if (!self.userInitiatedDisconnect) {
CXCallEndedReason reason = CXCallEndedReasonRemoteEnded;
if (error) {
reason = CXCallEndedReasonFailed;
}
[self.callKitProvider reportCallWithUUID:call.uuid endedAtDate:[NSDate date] reason:reason];
}
[self sendEventWithName:#"onSessionConnect" body:#"Disconnected"];
[self callDisconnected:call];
}
- (void)callDisconnected:(TVOCall *)call {
if ([call isEqual:self.activeCall]) {
self.activeCall = nil;
}
[self.activeCalls removeObjectForKey:call.uuid.UUIDString];
self.userInitiatedDisconnect = NO;
if (self.playCustomRingback) {
[self stopRingback];
}
}
- (void)call:(TVOCall *)call
didReceiveQualityWarnings:(NSSet<NSNumber *> *)currentWarnings
previousWarnings:(NSSet<NSNumber *> *)previousWarnings {
/**
* currentWarnings: existing quality warnings that have not been cleared yet
* previousWarnings: last set of warnings prior to receiving this callback
*
* Example:
* - currentWarnings: { A, B }
* - previousWarnings: { B, C }
* - intersection: { B }
*
* Newly raised warnings = currentWarnings - intersection = { A }
* Newly cleared warnings = previousWarnings - intersection = { C }
*/
NSMutableSet *warningIntersetction = [currentWarnings mutableCopy];
[warningIntersetction intersectSet:previousWarnings];
NSMutableSet *newWarnings = [currentWarnings mutableCopy];
[newWarnings minusSet:warningIntersetction];
if ([newWarnings count] > 0) {
[self qualityWarningUpdatePopup:newWarnings isCleared:NO];
}
NSMutableSet *clearedWarnings = [previousWarnings mutableCopy];
[clearedWarnings minusSet:warningIntersetction];
if ([clearedWarnings count] > 0) {
[self qualityWarningUpdatePopup:clearedWarnings isCleared:YES];
}
}
- (void)qualityWarningUpdatePopup:(NSSet *)warnings isCleared:(BOOL)cleared {
NSString *popupMessage = (cleared)? #"Warnings cleared:" : #"Warnings detected:";
for (NSNumber *warning in warnings) {
NSString *warningName = [self warningString:[warning unsignedIntValue]];
popupMessage = [popupMessage stringByAppendingString:[NSString stringWithFormat:#" %#", warningName]];
}
[UIView animateWithDuration:1.0f
animations:^{
} completion:^(BOOL finished) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:1.0 animations:^{
} completion:^(BOOL finished) {
}];
});
}];
}
- (NSString *)warningString:(TVOCallQualityWarning)qualityWarning {
switch (qualityWarning) {
case TVOCallQualityWarningHighRtt:
return #"high-rtt";
break;
case TVOCallQualityWarningHighJitter:
return #"high-jitter";
break;
case TVOCallQualityWarningHighPacketsLostFraction:
return #"high-packets-lost-fraction";
break;
case TVOCallQualityWarningLowMos:
return #"low-mos";
break;
case TVOCallQualityWarningConstantAudioInputLevel:
return #"constant-audio-input-level";
break;
default:
return #"Unknown warning";
break;
}
}
-(void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type {
NSLog(#"pushRegistry:didReceiveIncomingPushWithPayload:forType:");
if ([type isEqualToString:PKPushTypeVoIP]) {
// The Voice SDK will use main queue to invoke `cancelledCallInviteReceived:error` when delegate queue is not passed
if (![TwilioVoiceSDK handleNotification:payload.dictionaryPayload delegate:self delegateQueue:nil]) {
NSLog(#"This is not a valid Twilio Voice notification.");
}
else
{
}
}
}
/**
* This delegate method is available on iOS 11 and above. Call the completion handler once the
* notification payload is passed to the `TwilioVoice.handleNotification()` method.
*/
- (void)pushRegistry:(PKPushRegistry *)registry
didReceiveIncomingPushWithPayload:(PKPushPayload *)payload
forType:(PKPushType)type
withCompletionHandler:(void (^)(void))completion {
NSLog(#"pushRegistry:didReceiveIncomingPushWithPayload:forType:withCompletionHandler:");
// Save for later when the notification is properly handled.
self.incomingPushCompletionCallback = completion;
if ([type isEqualToString:PKPushTypeVoIP]) {
// The Voice SDK will use main queue to invoke `cancelledCallInviteReceived:error` when delegate queue is not passed
if (![TwilioVoiceSDK handleNotification:payload.dictionaryPayload delegate:self delegateQueue:nil]) {
NSLog(#"This is not a valid Twilio Voice notification.");
}
}
if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion < 13) {
// Save for later when the notification is properly handled.
self.incomingPushCompletionCallback = completion;
} else {
/**
* The Voice SDK processes the call notification and returns the call invite synchronously. Report the incoming call to
* CallKit and fulfill the completion before exiting this callback method.
*/
completion();
}
}
#pragma mark - AVAudioSession
- (void)toggleAudioRoute:(BOOL)toSpeaker {
// The mode set by the Voice SDK is "VoiceChat" so the default audio route is the built-in receiver. Use port override to switch the route.
self.audioDevice.block = ^ {
// We will execute `kDefaultAVAudioSessionConfigurationBlock` first.
kTVODefaultAVAudioSessionConfigurationBlock();
// Overwrite the audio route
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
if (toSpeaker) {
if (![session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error]) {
NSLog(#"Unable to reroute audio: %#", [error localizedDescription]);
}
} else {
if (![session overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error]) {
NSLog(#"Unable to reroute audio: %#", [error localizedDescription]);
}
}
};
self.audioDevice.block();
}
#pragma mark - CXProviderDelegate
- (void)providerDidReset:(CXProvider *)provider {
NSLog(#"providerDidReset:");
self.audioDevice.enabled = NO;
}
- (void)providerDidBegin:(CXProvider *)provider {
NSLog(#"providerDidBegin:");
}
- (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession {
NSLog(#"provider:didActivateAudioSession:");
self.audioDevice.enabled = YES;
}
- (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSession *)audioSession {
NSLog(#"provider:didDeactivateAudioSession:");
self.audioDevice.enabled = NO;
}
- (void)provider:(CXProvider *)provider timedOutPerformingAction:(CXAction *)action {
NSLog(#"provider:timedOutPerformingAction:");
}
- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action {
NSLog(#"provider:performStartCallAction:");
[self.callKitProvider reportOutgoingCallWithUUID:action.callUUID startedConnectingAtDate:[NSDate date]];
__weak typeof(self) weakSelf = self;
[self performVoiceCallWithUUID:action.callUUID client:nil completion:^(BOOL success) {
__strong typeof(self) strongSelf = weakSelf;
if (success) {
NSLog(#"performVoiceCallWithUUID successful");
[strongSelf.callKitProvider reportOutgoingCallWithUUID:action.callUUID connectedAtDate:[NSDate date]];
} else {
NSLog(#"performVoiceCallWithUUID failed");
}
[action fulfill];
}];
}
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
NSLog(#"provider:performAnswerCallAction:");
[self performAnswerVoiceCallWithUUID:action.callUUID completion:^(BOOL success) {
if (success) {
NSLog(#"performAnswerVoiceCallWithUUID successful");
} else {
NSLog(#"performAnswerVoiceCallWithUUID failed");
}
}];
[action fulfill];
}
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
NSLog(#"provider:performEndCallAction:");
TVOCallInvite *callInvite = self.activeCallInvites[action.callUUID.UUIDString];
TVOCall *call = self.activeCalls[action.callUUID.UUIDString];
if (callInvite) {
[callInvite reject];
[self.activeCallInvites removeObjectForKey:callInvite.uuid.UUIDString];
} else if (call) {
[call disconnect];
} else {
NSLog(#"Unknown UUID to perform end-call action with");
}
[action fulfill];
}
- (void)provider:(CXProvider *)provider performSetHeldCallAction:(CXSetHeldCallAction *)action {
TVOCall *call = self.activeCalls[action.callUUID.UUIDString];
if (call) {
[call setOnHold:action.isOnHold];
[action fulfill];
} else {
[action fail];
}
}
- (void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action {
TVOCall *call = self.activeCalls[action.callUUID.UUIDString];
if (call) {
[call setMuted:action.isMuted];
[action fulfill];
} else {
[action fail];
}
}
#pragma mark - CallKit Actions
- (void)reportIncomingCallFrom:(NSString *) from withUUID:(NSUUID *)uuid {
CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:from];
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
callUpdate.remoteHandle = callHandle;
callUpdate.supportsDTMF = YES;
callUpdate.supportsHolding = YES;
callUpdate.supportsGrouping = NO;
callUpdate.supportsUngrouping = NO;
callUpdate.hasVideo = NO;
[self.callKitProvider reportNewIncomingCallWithUUID:uuid update:callUpdate completion:^(NSError *error) {
if (!error) {
}
else {
}
}];
}
- (void)performVoiceCallWithUUID:(NSUUID *)uuid
client:(NSString *)client
completion:(void(^)(BOOL success))completionHandler {
__weak typeof(self) weakSelf = self;
TVOConnectOptions *connectOptions = [TVOConnectOptions optionsWithAccessToken:kAccessToken block:^(TVOConnectOptionsBuilder *builder) {
__strong typeof(self) strongSelf = weakSelf;
builder.params = #{kTwimlParamTo:phoneNumber};
builder.uuid = uuid;
}];
TVOCall *call = [TwilioVoiceSDK connectWithOptions:connectOptions delegate:self];
if (call) {
self.activeCall = call;
self.activeCalls[call.uuid.UUIDString] = call;
}
self.callKitCompletionCallback = completionHandler;
}
- (void)performAnswerVoiceCallWithUUID:(NSUUID *)uuid
completion:(void(^)(BOOL success))completionHandler {
TVOCallInvite *callInvite = self.activeCallInvites[uuid.UUIDString];
NSAssert(callInvite, #"No CallInvite matches the UUID");
TVOAcceptOptions *acceptOptions = [TVOAcceptOptions optionsWithCallInvite:callInvite block:^(TVOAcceptOptionsBuilder *builder) {
builder.uuid = callInvite.uuid;
}];
TVOCall *call = [callInvite acceptWithOptions:acceptOptions delegate:self];
if (!call) {
completionHandler(NO);
} else {
self.callKitCompletionCallback = completionHandler;
self.activeCall = call;
self.activeCalls[call.uuid.UUIDString] = call;
}
[self.activeCallInvites removeObjectForKey:callInvite.uuid.UUIDString];
if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion < 13) {
[self incomingPushHandled];
}
}
#pragma mark - Ringtone
- (void)playRingback {
NSString *ringtonePath = [[NSBundle mainBundle] pathForResource:#"ringtone" ofType:#"wav"];
if ([ringtonePath length] <= 0) {
return;
}
NSError *error;
self.ringtonePlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:ringtonePath] error:&error];
if (error != nil) {
} else {
self.ringtonePlayer.delegate = self;
self.ringtonePlayer.numberOfLoops = -1;
self.ringtonePlayer.volume = 1.0f;
[self.ringtonePlayer play];
}
}
- (void)stopRingback {
if (!self.ringtonePlayer.isPlaying) {
return;
}
[self.ringtonePlayer stop];
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
if (flag) {
NSLog(#"Audio player finished playing successfully");
} else {
NSLog(#"Audio player finished playing with some error");
}
}
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error {
NSLog(#"Decode error occurred: %#", error);
}
#end
#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
#import "NearbyHandler.h"
#import <GNSMessages.h>
static NSString * const kMyAPIKey = #"********";
#interface NearbyHandler ()
#property(nonatomic) GNSPermission *nearbyPermission;
#property(nonatomic) GNSMessageManager *messageMgr;
#property(nonatomic) id<GNSPublication> publication;
#property(nonatomic) id<GNSSubscription> subscription;
#end
#implementation NearbyHandler
- (instancetype)init
{
if (self = [super init])
{
[self configureNearbyFramework];
}
return self;
}
- (void)configureNearbyFramework
{
__weak __typeof__(self) weakSelf = self;
_nearbyPermission = [[GNSPermission alloc] initWithChangedHandler:^(BOOL granted) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
if(strongSelf)
{
NSLog(#"Nearby Permission:%#", granted ? #"YES" : #"NO");
}
}];
[GNSMessageManager setDebugLoggingEnabled:YES];
}
#pragma mark - START_SCAN_PUBLISH_METHODS
- (void)startPublishingDefaultData
{
NSDictionary *dataDict = #{#"device_name":#"cefgrstd"};
NSError *error = nil;
NSData *dataFromDict = [NSJSONSerialization dataWithJSONObject:dataDict
options:NSJSONWritingPrettyPrinted
error:&error];
if(!error)
{
[self pusblishData:dataFromDict];
}
}
- (void)publishSpecificData:(NSString *)stringData
{
if(stringData.length)
{
NSData *pubData = [stringData dataUsingEncoding:NSUTF8StringEncoding];
[self pusblishData:pubData];
}
}
- (void)pusblishData:(NSData *)data
{
if(!_publication)
{
// This means that the data was not sent
if(![GNSPermission isGranted])
{
[self configureNearbyFramework];
}
__weak __typeof__(self) weakSelf = self;
GNSMessage *dataForPublish = [GNSMessage messageWithContent:data];
_publication = [self.messageMgr publicationWithMessage:dataForPublish
paramsBlock:^(GNSPublicationParams *params) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
if(strongSelf)
{
params.strategy = [strongSelf getStrategy];
}
}];
// The reason why we have to scan after publishing is to check if there is a faliure of data transmission
[self startScanning];
}
}
- (void)startScanning
{
if(![GNSPermission isGranted])
{
[self configureNearbyFramework];
}
__weak __typeof__(self) weakSelf = self;
_subscription = [_messageMgr
subscriptionWithMessageFoundHandler:^(GNSMessage *message) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
// Here
}
}
messageLostHandler:^(GNSMessage *message) {
// should we dealloc the _publication here again?
__strong __typeof(weakSelf) strongSelf = weakSelf;
if(strongSelf)
{
// Here we loose the message
}
}
paramsBlock:^(GNSSubscriptionParams *params) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
if(strongSelf)
{
params.strategy = [strongSelf getStrategy];
}
}];
}
/// Stops sharing and scanning.
- (void)stopSharingAndScanning {
if(_publication)
_publication = nil;
if(_subscription)
_subscription = nil;
if(_messageMgr)
_messageMgr = nil;
}
#pragma mark - PERMISSION
/// Toggles the permission state of Nearby.
- (void)changeNearbyPermission {
[GNSPermission setGranted:![GNSPermission isGranted]];
}
#pragma mark - GETTERS
- (GNSMessageManager *)messageMgr
{
if(!_messageMgr)
{
_messageMgr = [[GNSMessageManager alloc] initWithAPIKey:kMyAPIKey paramsBlock:^(GNSMessageManagerParams *params) {
params.microphonePermissionErrorHandler = ^(BOOL hasError) {
NSLog(#"Microphone Permission Error:%#", hasError ? #"YES" : #"NO");
};
params.bluetoothPowerErrorHandler = ^(BOOL hasError) {
NSLog(#"Bluetooth Power Error:%#", hasError ? #"YES" : #"NO");
};
params.bluetoothPermissionErrorHandler = ^(BOOL hasError) {
NSLog(#"Bluetooth Permission Error:%#", hasError ? #"YES" : #"NO");
};
}];
}
return _messageMgr;
}
- (GNSStrategy *)getStrategy
{
return [GNSStrategy strategyWithParamsBlock:^(GNSStrategyParams *params) {
params.allowInBackground = NO;
params.discoveryMediums = kGNSDiscoveryMediumsBLE;
params.discoveryMode = (_shouldScanForPeers) ? kGNSDiscoveryModeScan : kGNSDiscoveryModeDefault;
}];
}
I'm creating an object of this Nearby handler class in a View Controller and then passing it to another View Controller where this object would either scan or broadcast, i.e the startScanning/startPublishingDefaultData method is called in viewDidAppear method of the passed View Controller. But my nearby permission dialog never appears, as a result of which the scanning/ publishing doesn't happen. Can you show me where the problem exists in code?
P.S: I thought that my key was invalid but when I applied the same key in the demo project for nearby (https://github.com/googlesamples/ios-nearby), it works properly.
The policy in Nearby Messages is to ask for permission only when iOS requires permission for something (such as for microphone or background BLE). In your case, you're only using BLE in the foreground, and there are no iOS permissions needed for this mode, so Nearby doesn't ask for permission.
I'm getting the error instance method '-callDelegateOnMainThread:withArg:error:' not found what should I do here? I merged in some achievement code into Ray Wenderlichs turn based multiplayer code.
The functions are part of the following interface:
#interface GCTurnBasedMatchHelper : NSObject <GKTurnBasedMatchmakerViewControllerDelegate, GKTurnBasedEventHandlerDelegate, GKAchievementViewControllerDelegate, MFMessageComposeViewControllerDelegate> {
BOOL gameCenterAvailable;
BOOL userAuthenticated;
UIViewController *presentingViewController;
NSMutableDictionary* earnedAchievementCache;
GKTurnBasedMatch *currentMatch;
//id <GCTurnBasedMatchHelperDelegate> delegate;
}
Here are the functions
- (void) submitAchievement: (NSString*) identifier percentComplete: (double) percentComplete
{
//GameCenter check for duplicate achievements when the achievement is submitted, but if you only want to report
// new achievements to the user, then you need to check if it's been earned
// before you submit. Otherwise you'll end up with a race condition between loadAchievementsWithCompletionHandler
// and reportAchievementWithCompletionHandler. To avoid this, we fetch the current achievement list once,
// then cache it and keep it updated with any new achievements.
if(self.earnedAchievementCache == NULL)
{
[GKAchievement loadAchievementsWithCompletionHandler: ^(NSArray *scores, NSError *error)
{
if(error == NULL)
{
NSMutableDictionary* tempCache = [NSMutableDictionary dictionaryWithCapacity: [scores count]];
for (GKAchievement* score in scores)
{
[tempCache setObject: score forKey: score.identifier];
}
self.earnedAchievementCache = tempCache;
[self submitAchievement: identifier percentComplete: percentComplete];
}
else
{
//Something broke loading the achievement list. Error out, and we'll try again the next time achievements submit.
[self callDelegateOnMainThread: #selector(achievementSubmitted:error:) withArg: NULL error: error];
}
}];
}
else
{
//Search the list for the ID we're using...
GKAchievement* achievement = [self.earnedAchievementCache objectForKey: identifier];
if(achievement != NULL)
{
if((achievement.percentComplete >= 100.0) || (achievement.percentComplete >= percentComplete))
{
//Achievement has already been earned so we're done.
achievement= NULL;
}
achievement.percentComplete= percentComplete;
}
else
{
achievement = [[[GKAchievement alloc] initWithIdentifier: identifier] autorelease];
achievement.percentComplete= percentComplete;
//Add achievement to achievement cache...
[self.earnedAchievementCache setObject: achievement forKey: achievement.identifier];
}
if(achievement != NULL)
{
//Submit the Achievement...
[achievement reportAchievementWithCompletionHandler: ^(NSError *error)
{
[self callDelegateOnMainThread: #selector(achievementSubmitted:error:) withArg: achievement error: error];
}];
}
}
}
- (void) achievementSubmitted: (GKAchievement*) ach error:(NSError*) error;
{
if((error == NULL) && (ach != NULL))
{
if (ach.percentComplete == 100.0) {
//UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Achievement Earned!"
// message:(#"%#",ach.identifier)
// delegate:nil
// cancelButtonTitle:#"OK"
// otherButtonTitles:nil];
//[alert show];
//[alert release];
TRACE("achievement submitted %s\n", [ach.identifier UTF8String]);
}
}
else
{
// Achievement Submission Failed.
printf("Achievement Submission Failed\n");
}
}
Looks like this is the missing code you are looking for. Just put it inside of your GCTurnBasedMatchHelper.m file.
- (void) callDelegateOnMainThread: (SEL) selector withArg: (id) arg error: (NSError*) err
{
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[self callDelegate: selector withArg: arg error: err];
});
}
- (void) callDelegate: (SEL) selector withArg: (id) arg error: (NSError*) err
{
assert([NSThread isMainThread]);
if([delegate respondsToSelector: selector])
{
if(arg != NULL)
{
[delegate performSelector: selector withObject: arg withObject: err];
}
else
{
[delegate performSelector: selector withObject: err];
}
}
else
{
NSLog(#"Missed Method");
}
}
Just add the method which doesn't exist.
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...