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
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
#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.
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.
So, I have all multipeer connectivity related code in the main thread. I have a MCSession, MCNearbyServiceAdvertiser, and a MCNearbyServiceBrowser. These are all created with peerID, and I make sure only one sends an invitation.
The session becomes connected. My problems is that, two clients take around 20-30 seconds to connect. This is unacceptable. The clients are on good Wifi, and bluetooth. I want the browsing, invitation handler, and connection to take place within 1 second. Does anyone have any idea what is slowing things down?
Code is exactly as provided here and I also implemented certificateHandler(YES)
#interface SessionController () // Class extension
#property (nonatomic, strong) MCPeerID *peerID;
#property (nonatomic, strong) MCSession *session;
#property (nonatomic, strong) MCNearbyServiceAdvertiser *serviceAdvertiser;
#property (nonatomic, strong) MCNearbyServiceBrowser *serviceBrowser;
// Connected peers are stored in the MCSession
// Manually track connecting and disconnected peers
#property (nonatomic, strong) NSMutableOrderedSet *connectingPeersOrderedSet;
#property (nonatomic, strong) NSMutableOrderedSet *disconnectedPeersOrderedSet;
#end
#implementation SessionController
static NSString * const kMCSessionServiceType = #"mcsessionp2p";
#pragma mark - Initializer
- (instancetype)init
{
self = [super init];
if (self)
{
_peerID = [[MCPeerID alloc] initWithDisplayName:[[UIDevice currentDevice] name]];
_connectingPeersOrderedSet = [[NSMutableOrderedSet alloc] init];
_disconnectedPeersOrderedSet = [[NSMutableOrderedSet alloc] init];
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
// Register for notifications
[defaultCenter addObserver:self
selector:#selector(startServices)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[defaultCenter addObserver:self
selector:#selector(stopServices)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[self startServices];
_displayName = self.session.myPeerID.displayName;
}
return self;
}
#pragma mark - Memory management
- (void)dealloc
{
// Unregister for notifications on deallocation.
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Nil out delegates
_session.delegate = nil;
_serviceAdvertiser.delegate = nil;
_serviceBrowser.delegate = nil;
}
#pragma mark - Override property accessors
- (NSArray *)connectedPeers
{
return self.session.connectedPeers;
}
- (NSArray *)connectingPeers
{
return [self.connectingPeersOrderedSet array];
}
- (NSArray *)disconnectedPeers
{
return [self.disconnectedPeersOrderedSet array];
}
#pragma mark - Private methods
- (void)setupSession
{
// Create the session that peers will be invited/join into.
_session = [[MCSession alloc] initWithPeer:self.peerID];
self.session.delegate = self;
// Create the service advertiser
_serviceAdvertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:self.peerID
discoveryInfo:nil
serviceType:kMCSessionServiceType];
self.serviceAdvertiser.delegate = self;
// Create the service browser
_serviceBrowser = [[MCNearbyServiceBrowser alloc] initWithPeer:self.peerID
serviceType:kMCSessionServiceType];
self.serviceBrowser.delegate = self;
}
- (void)teardownSession
{
[self.session disconnect];
[self.connectingPeersOrderedSet removeAllObjects];
[self.disconnectedPeersOrderedSet removeAllObjects];
}
- (void)startServices
{
[self setupSession];
[self.serviceAdvertiser startAdvertisingPeer];
[self.serviceBrowser startBrowsingForPeers];
}
- (void)stopServices
{
[self.serviceBrowser stopBrowsingForPeers];
[self.serviceAdvertiser stopAdvertisingPeer];
[self teardownSession];
}
- (void)updateDelegate
{
[self.delegate sessionDidChangeState];
}
- (NSString *)stringForPeerConnectionState:(MCSessionState)state
{
switch (state) {
case MCSessionStateConnected:
return #"Connected";
case MCSessionStateConnecting:
return #"Connecting";
case MCSessionStateNotConnected:
return #"Not Connected";
}
}
#pragma mark - MCSessionDelegate protocol conformance
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state
{
NSLog(#"Peer [%#] changed state to %#", peerID.displayName, [self stringForPeerConnectionState:state]);
switch (state)
{
case MCSessionStateConnecting:
{
[self.connectingPeersOrderedSet addObject:peerID];
[self.disconnectedPeersOrderedSet removeObject:peerID];
break;
}
case MCSessionStateConnected:
{
[self.connectingPeersOrderedSet removeObject:peerID];
[self.disconnectedPeersOrderedSet removeObject:peerID];
break;
}
case MCSessionStateNotConnected:
{
[self.connectingPeersOrderedSet removeObject:peerID];
[self.disconnectedPeersOrderedSet addObject:peerID];
break;
}
}
[self updateDelegate];
}
- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID
{
// Decode the incoming data to a UTF8 encoded string
NSString *receivedMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"didReceiveData %# from %#", receivedMessage, peerID.displayName);
}
- (void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress
{
NSLog(#"didStartReceivingResourceWithName [%#] from %# with progress [%#]", resourceName, peerID.displayName, progress);
}
- (void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error
{
NSLog(#"didFinishReceivingResourceWithName [%#] from %#", resourceName, peerID.displayName);
// If error is not nil something went wrong
if (error)
{
NSLog(#"Error [%#] receiving resource from %# ", [error localizedDescription], peerID.displayName);
}
else
{
// No error so this is a completed transfer. The resources is located in a temporary location and should be copied to a permenant location immediately.
// Write to documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *copyPath = [NSString stringWithFormat:#"%#/%#", [paths firstObject], resourceName];
if (![[NSFileManager defaultManager] copyItemAtPath:[localURL path] toPath:copyPath error:nil])
{
NSLog(#"Error copying resource to documents directory");
}
else
{
// Get a URL for the path we just copied the resource to
NSURL *url = [NSURL fileURLWithPath:copyPath];
NSLog(#"url = %#", url);
}
}
}
// Streaming API not utilized in this sample code
- (void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID
{
NSLog(#"didReceiveStream %# from %#", streamName, peerID.displayName);
}
#pragma mark - MCNearbyServiceBrowserDelegate protocol conformance
// Found a nearby advertising peer
- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info
{
NSString *remotePeerName = peerID.displayName;
NSLog(#"Browser found %#", remotePeerName);
MCPeerID *myPeerID = self.session.myPeerID;
BOOL shouldInvite = ([myPeerID.displayName compare:remotePeerName] == NSOrderedDescending);
if (shouldInvite)
{
NSLog(#"Inviting %#", remotePeerName);
[browser invitePeer:peerID toSession:self.session withContext:nil timeout:30.0];
}
else
{
NSLog(#"Not inviting %#", remotePeerName);
}
[self updateDelegate];
}
- (void)browser:(MCNearbyServiceBrowser *)browser lostPeer:(MCPeerID *)peerID
{
NSLog(#"lostPeer %#", peerID.displayName);
[self.connectingPeersOrderedSet removeObject:peerID];
[self.disconnectedPeersOrderedSet addObject:peerID];
[self updateDelegate];
}
- (void)browser:(MCNearbyServiceBrowser *)browser didNotStartBrowsingForPeers:(NSError *)error
{
NSLog(#"didNotStartBrowsingForPeers: %#", error);
}
#pragma mark - MCNearbyServiceAdvertiserDelegate protocol conformance
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler
{
NSLog(#"didReceiveInvitationFromPeer %#", peerID.displayName);
invitationHandler(YES, self.session);
[self.connectingPeersOrderedSet addObject:peerID];
[self.disconnectedPeersOrderedSet removeObject:peerID];
[self updateDelegate];
}
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didNotStartAdvertisingPeer:(NSError *)error
{
NSLog(#"didNotStartAdvertisingForPeers: %#", error);
}
#end
I've noticed this issue to and i can't seem to figure out whats happening here, it appears when debugging that there is a huge delay between the notification for state changing being fired, perhaps something to do with the view itself.
Update: Well i think i figured the issues out after a bit of reading, the response is pretty much instant now, in the notification in the view i have pushed the process back to the main thread like so:
-(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) {
// add the user
[arrConnectedDevices addObject:peerDisplayName];
}
else if (state == MCSessionStateNotConnected){
// do we have connections
if ([arrConnectedDevices count] > 0) {
int indexOfPeer = [arrConnectedDevices indexOfObject:peerDisplayName];
[arrConnectedDevices removeObjectAtIndex:indexOfPeer];
}
}
}
// push to main queue for speedy response
dispatch_async(dispatch_get_main_queue(), ^(void) {
[collView reloadData];
BOOL peersExist = ([[appDelegate.mcManager.session connectedPeers] count] == 0);
NSLog(#"PEER COUNT IS %lu",(unsigned long)[[appDelegate.mcManager.session connectedPeers] count]);
[disconnectButton setEnabled:!peersExist];
if ([disconnectButton isEnabled]) {
[disconnectButton setBackgroundColor:[UIColor colorWithRed:(51/255.0) green:(202/255.0) blue:(168/255.0) alpha:1.0]];
}
else{
[disconnectButton setBackgroundColor:[UIColor colorWithRed:(107/255.0) green:(107/255.0) blue:(107/255.0) alpha:1.0]];
}
});
}
Hope this helps anyone who ran into the issues.
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...