#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.
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
My PhotosListCollectionViewController.h file:
#interface PhotosListCollectionViewController : UICollectionViewController <UICollectionViewDelegateFlowLayout> {
FooterView *footerView;
PhotosListCollectionViewViewModel *photosListCollectionViewViewModel;
}
#property (strong, nonatomic) NSString *userQuery;
#end
In PhotosListCollectionViewController.m (look comment):
#implementation PhotosListCollectionViewController
- (void)viewDidLoad {
[super viewDidLoad];
footerView = [[FooterView alloc] init];
photosListCollectionViewViewModel = [[PhotosListCollectionViewViewModel alloc] initWithUserQuery:_userQuery];
[self.collectionView registerClass:FooterView.class forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:NSStringFromClass([FooterView class])];
__weak __typeof(self)weakSelf = self;
photosListCollectionViewViewModel.getNextPage = ^(NSError *error) {
if (nil == error) {
[weakSelf.collectionView reloadData];
} else {
[weakSelf showAlertWithTitle:error.localizedDescription message:#"Try again."];
}
[self->footerView hideLoader]; // warning in this line.
};
}
How to resolve my problem? I read another questions, but they did not solve my problem.
Change to
[weakSelf->footerView hideLoader];
get error
Dereferencing a __weak pointer is not allowed due to possible null
value caused by race condition, assign it to strong variable first
Thanks #matt for help me. Decision is:
__weak __typeof(self)weakSelf = self;
_photosListCollectionViewViewModel.getNextPage = ^(NSError *error) {
if (nil == error) {
[weakSelf.collectionView reloadData];
} else {
[weakSelf showAlertWithTitle:error.localizedDescription message:#"Повторите попытку."];
}
__typeof(self)strongSelf = weakSelf;
[strongSelf->footerView hideLoader];
};
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...
Hi i Intigrate SpotifyLib CocoaLibSpotify iOS Library 17-20-26-630 into my Project. I open its SPLoginViewController using Bellow Method:-
-(void)OpenSpotify
{
NSError *error = nil;
[SPSession initializeSharedSessionWithApplicationKey:[NSData dataWithBytes:&g_appkey length:g_appkey_size]
userAgent:#"com.mycomp.spotify"
loadingPolicy:SPAsyncLoadingImmediate
error:&error];
if (error != nil) {
NSLog(#"CocoaLibSpotify init failed: %#", error);
abort();
}
[[SPSession sharedSession] setDelegate:self];
[self performSelector:#selector(showLogin) withObject:nil afterDelay:0.0];
}
-(void)showLogin
{
SPLoginViewController *controller = [SPLoginViewController loginControllerForSession:[SPSession sharedSession]];
controller.allowsCancel = YES;
//controller.view.frame=;
[self presentViewController:controller animated:YES completion:nil];
}
At First time that Appear Spotify Login Screen. After that I tap On Cancel Button, and Try to open again login screen then i got crash EXC_BAD_EXE at this line. sp_error createErrorCode = sp_session_create(&config, &_session);
UPDATE
I Found exet where is got BAD_EXC
in this method
+(void)dispatchToLibSpotifyThread:(dispatch_block_t)block waitUntilDone:(BOOL)wait {
NSLock *waitingLock = nil;
if (wait) waitingLock = [NSLock new];
// Make sure we only queue one thing at a time, and only
// when the runloop is ready for it.
[runloopReadyLock lockWhenCondition:1];
CFRunLoopPerformBlock(libspotify_runloop, kCFRunLoopDefaultMode, ^() {
[waitingLock lock];
if (block) { #autoreleasepool { block(); } }
[waitingLock unlock];
});
if (CFRunLoopIsWaiting(libspotify_runloop)) {
CFRunLoopSourceSignal(libspotify_runloop_source);
CFRunLoopWakeUp(libspotify_runloop);
}
[runloopReadyLock unlock]; // at hear when my debug poin reach after pass this i got bad_exc
if (wait) {
[waitingLock lock];
[waitingLock unlock];
}
}
after doing lots of search i got Solution i check that whether the session already exists then i put if condition like:-
-(void)OpenSpotify
{
SPSession *session = [SPSession sharedSession];
if (!session) {
NSError *error = nil;
[SPSession initializeSharedSessionWithApplicationKey:[NSData dataWithBytes:&g_appkey length:g_appkey_size]
userAgent:#"com.mycomp.spotify"
loadingPolicy:SPAsyncLoadingImmediate
error:&error];
if (error != nil) {
NSLog(#"CocoaLibSpotify init failed: %#", error);
abort();
}
[[SPSession sharedSession] setDelegate:self];
}
[self performSelector:#selector(showLogin) withObject:nil afterDelay:0.0];
}
-(void)showLogin
{
SPLoginViewController *controller = [SPLoginViewController loginControllerForSession:[SPSession sharedSession]];
controller.allowsCancel = YES;
[self presentViewController:controller animated:YES completion:nil];
}
Now no crash and working fine.
Using Ray Wenderlich's QRCode reader from Chapter 22 of iOS7 Tutorials, I am successfully reading QRCodes for my current app. I am now extending it that upon successfully reading a QRCode, I want to store the stringValue of the AVMetadataMachineReadableCodeObject that was read, segue to a new view, and use that data on the new view, more or less exactly how most QRCode reader apps (like RedLaser, etc...) process barcodes and QRCodes.
However, I call [captureSession stopRunning] (so that it does not read any more QRCodes and trigger additional segues) and there is a 10+ second hang. I have tried to implement an async call per this SO question, however to no avail. I have also looked at these SO Questions and they seem not to be appropriate for this purpose.
Does anyone have an idea how to remove this hanging?
Here is the code:
#import "BMQRCodeReaderViewController.h"
#import "NSString+containsString.h"
#import "BMManualExperimentDataEntryViewController.h"
#import AVFoundation;
#interface BMQRCodeReaderViewController ()
<AVCaptureMetadataOutputObjectsDelegate>
#end
#implementation BMQRCodeReaderViewController {
AVCaptureSession *_captureSession;
AVCaptureDevice *_videoDevice;
AVCaptureDeviceInput *_videoInput;
AVCaptureVideoPreviewLayer *_previewLayer;
BOOL _running;
AVCaptureMetadataOutput *_metadataOutput;
}
- (void)setupCaptureSession { // 1
if (_captureSession) return;
// 2
_videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if (!_videoDevice) {
NSLog(#"No video camera on this device!"); return;
}
// 3
_captureSession = [[AVCaptureSession alloc] init];
// 4
_videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:_videoDevice error:nil];
// 5
if ([_captureSession canAddInput:_videoInput]) { [_captureSession addInput:_videoInput];
}
// 6
_previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
_previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
_metadataOutput = [[AVCaptureMetadataOutput alloc] init];
dispatch_queue_t metadataQueue = dispatch_queue_create("com.razeware.ColloQR.metadata", 0);
[_metadataOutput setMetadataObjectsDelegate:self queue:metadataQueue];
if ([_captureSession canAddOutput:_metadataOutput]) { [_captureSession addOutput:_metadataOutput];
}
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection {
// This fancy BOOL is just helping me fire the segue when the correct string is found
__block NSNumber *didFind = [NSNumber numberWithBool:NO];
[metadataObjects enumerateObjectsUsingBlock:^(AVMetadataObject *obj, NSUInteger idx, BOOL *stop) {
AVMetadataMachineReadableCodeObject *readableObject = (AVMetadataMachineReadableCodeObject *)obj;
NSLog(#"Metadata: %#", readableObject);
// [ containsString is a category I extended for NSString, just FYI
if ([readableObject.stringValue containsString:#"CorrectString"]) {
didFind = [NSNumber numberWithBool:YES];
NSLog(#"Found it");
_testName = #"NameOfTest";
*stop = YES;
return;
}
}];
if ([didFind boolValue]) {
NSLog(#"Confirming we found it");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self stopRunning];
});
_labelTestName.text = _testName;
[self performSegueWithIdentifier:#"segueFromFoundQRCode" sender:self];
}
else {
NSLog(#"Did not find it");
}
}
- (void)startRunning {
if (_running)
return;
[_captureSession startRunning];
_metadataOutput.metadataObjectTypes = _metadataOutput.availableMetadataObjectTypes;
_running = YES;
}
- (void)stopRunning {
if (!_running) return;
[_captureSession stopRunning];
_running = NO;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setupCaptureSession];
[self setupNavBar];
[self startRunning];
_previewLayer.frame = _previewView.bounds;
[_previewView.layer addSublayer:_previewLayer];
}
I have successively solved the issue. The issue was that the delegate method call
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection
is running in the background. This was determined with a [NSThread isMainThread] call, which it fails.
The solution is to find the proper stringValue from the QRCode, stop your captureSession in the background, THEN call your segue on the main thread. The solution looks as such:
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection {
// This fancy BOOL is just helping me fire the segue when the correct string is found
__block NSNumber *didFind = [NSNumber numberWithBool:NO];
[metadataObjects enumerateObjectsUsingBlock:^(AVMetadataObject *obj, NSUInteger idx, BOOL *stop) {
AVMetadataMachineReadableCodeObject *readableObject = (AVMetadataMachineReadableCodeObject *)obj;
NSLog(#"Metadata: %#", readableObject);
if ([NSThread isMainThread]) {
NSLog(#"Yes Main Thread");
}
else {
NSLog(#"Not main thread");
}
// [ containsString is a category I extended for NSString, just FYI
if ([readableObject.stringValue containsString:#"Biomeme"]) {
//NSLog(#"this is a test: %#", getTestName);
didFind = [NSNumber numberWithBool:YES];
NSLog(#"Found it");
_testName = readableObject.stringValue;
*stop = YES;
return;
}
}];
if ([didFind boolValue]) {
NSLog(#"Confirming we found it");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSDate *start = [NSDate date];
[self stopRunning];
NSLog(#"time took: %f", -[start timeIntervalSinceNow]);
// *** Here is the key, make your segue on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"segueFromFoundQRCode" sender:self];
_labelTestName.text = _testName;
});
});
}
else {
NSLog(#"Did not find it");
}
}