why isn't didReceiveRemoteNotification being fired in my react-native app? - objective-c

I'm building a react-native app. Every time our cloud sends an APNS message, we want to increment the badge by 1.
I'm using the react-native PushNotificationIOS module to handle messages, but there's a known limitation with react-native, where the JS bridge is deactivated when the app is in the background. As a result, react-native application code that wants to manipulate the badge isn't executed, because the notification event is never delivered to the javascript.
It's important that this badge operation occur even when the app is not in the foreground, so I decided to implement the badge-incrementing logic in Objective-C, directly in AppDelegate.m. (Note: I don't understand ObjC, and can only loosely follow it.)
Here's the entirety of didReceiveRemoteNotification from AppDelegate.m:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
{
// --- BEGIN CODE I ADDED --- //
// increment badge number; must be written in ObjC b/c react-native JS bridge is deactivated when app in background
NSLog(#"remote notification LINE 1");
NSUInteger currentBadgeNum = [UIApplication sharedApplication].applicationIconBadgeNumber;
NSLog(#"currentBadgeNum = %d", currentBadgeNum);
NSUInteger newBadgeNum = currentBadgeNum + 1; // to increment by .badge: ... + [[[notification objectForKey:#"aps"] objectForKey: #"badge"] intValue]
NSLog(#"newBadgeNum = %d", newBadgeNum);
[UIApplication sharedApplication].applicationIconBadgeNumber = newBadgeNum;
NSLog(#"done updating badge; now notifying react-native");
// --- END CODE I ADDED --- //
// forward notification on to react-native code
[RCTPushNotificationManager didReceiveRemoteNotification:notification];
}
When I test it, none of the log statements appear in Xcode when the app is backgrounded. However, when the app is active, the log statements appear and the routine works exactly as expected (and the notification is delivered to the javascript code as well).
I believe I've already configured the app to receive push-notifications; here's the relevant section from my Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
What am I missing? Is there a problem with my code, or am I misunderstanding the nature of didReceiveRemoteNotification?
Any help is appreciated.

Related

"[GCController controllers]" does not contain any controllers that were connected prior to application launch

"[GCController controllers]" does not contain any controllers that were connected prior to application launch
TLDR;
I am trying to implement gamepad input on macOS using the Game Controller Framework. When invoked in my code, [GameController controllers] always returns an empty list until new controllers are connected. It never reflects gamepads connected to macOS prior to application launch, except if you disconnect them and reconnect them while the app is running. Does anyone know what I need to do to make controllers populate with pre-launch connections?
Full question
Now that Apple has added support for Xbox and Playstation controllers to the GameController framework, I'm trying to use it for gamepad input on a C++ game engine I'm developing. I'm using the framework instead of IOKit in order to "future-proof" my games to support additional controller types in the future, as well as to simplify my own input handling code.
Like many other game engines, I've foregone using NSApplicationMain() and nib files in favor of implementing my own event loop and setting up my game window programmatically. While my "Windows style" event loop appears to be working correctly, I've discovered that [GCController controllers] does not. The array it returns is always empty at launch, and will only ever reflect controllers that are connected while the game is running. Disconnecting a pre-connected controller does not trigger my GCControllerDidDisconnectNotification callback.
Here is a simplified version of my event loop:
int main(int argc, const char * argv[])
{
#autoreleasepool
{
// Create application
[NSApplication sharedApplication];
// Set up custom app delegate
CustomAppDelegate * delegate = [[CustomAppDelegate alloc] init];
[NSApp setDelegate:delegate];
// Activate and launch app
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp setPresentationOptions:NSApplicationPresentationDefault];
[NSApp activateIgnoringOtherApps:YES]; // Strictly speaking, not necessary
[NSApp finishLaunching]; // NSMenu is set up at this point in applicationWillFinishLaunching:.
// Initialize game engine (window is created here)
GenericEngineCode_Init(); // <-- Where I want to call [GCController controllers]
NSEvent *e;
do
{
do
{
// Pump messages
e = [NSApp nextEventMatchingMask: NSEventMaskAny
untilDate: nil
inMode: NSDefaultRunLoopMode
dequeue: YES];
if (e)
{
[NSApp sendEvent: e];
[NSApp updateWindows];
}
} while (e);
} while (GenericEngineCode_Run()); // Steps the engine, returns false when quitting.
GenericEngineCode_Cleanup();
}
return 0;
}
I've confirmed that even when using [NSApp run] instead of [NSApp finishLaunching], the behavior is the same. As best as I can tell, the problem is that there's something NSApplicationMain() does that I'm not doing, but that function is a black box -- I can't identify what I need to do to get controllers to populate correctly. Does anyone know what I'm missing?
The closest thing I could find to an explanation of this problem is this answer, which suggests that my app isn't getting didBecomeActive notifications, or that at the least, the private _GCControllerManager isn't getting a CBApplicationDidBecomeActive message. I'm not a professional macOS developer, though: I don't know if this actually applies to my situation, or how I'd go about correcting the problem if it does.
After a huge amount of time searching, I found the answer on my own. It turns out that my code wasn't the problem -- the problem was that my Info.plist file was having its CFBundleIdentifier value stripped out due to a problem with my build system. It appears that the Game Controller Framework needs the bundle identifier to correctly populate [GCController controllers] at launch. While a missing CFBundleIdentifier would have been a problem anyway, as a Windows person it didn't occur to me that the identifier might be used for things besides the App Store, so I let it slide until now.
If someone else has this problem, make sure that CFBundleIdentifier isn't missing or empty in Info.plist in your assembled app bundle. In my case with Premake, I had to manually set PRODUCT_BUNDLE_IDENTIFIER with xcodebuildsettings so that $(PRODUCT_BUNDLE_IDENTIFIER) would get properly replaced in Info.plist.

token pushwoosh is nil

in my app a pushNotificationManager give me a pushToken equal nil... only with IOS 8.1.3.... device is ipad3. With other device (or with other IOS version) my test it's ok..
i used this code to call getPushToken...
[[PushNotificationManager pushManager] getPushToken]
and in my appDelegate I have
-(void) settingPushwoosh:(NSDictionary *)launchOptions {
// set custom delegate for push handling, in our case - view controller
PushNotificationManager * pushManager = [PushNotificationManager pushManager];
pushManager.delegate = self;
// handling push on app start
[[PushNotificationManager pushManager] handlePushReceived:launchOptions];
// make sure we count app open in Pushwoosh stats
[[PushNotificationManager pushManager] sendAppOpen];
// register for push notifications!
[[PushNotificationManager pushManager] registerForPushNotifications];
}
Do you receive delegate callback when push token received? Sometimes you need to wait a little as this is asynchronous operation.

How to detect microphone input permission refused in iOS 7

I would like to detect when a user refused the microphone permission on my iOS application.
I only get this value when I try to record the microphone: -120.000000 db
But before to get this I have to set up an AVAudioSession. Is there another function?
And I got this message in the output:
Microphone input permission refused - will record only silence
Thanks.
If you are still compiling with iOS SDK 6.0 (as I am) you have to be a bit more indirect than #Luis E. Prado, as the requestRecordPermission method doesn't exist.
Here's how I did it. Remove the autorelease bit if you're using ARC. On iOS6 nothing happens, and on iOS7 either the 'microphone is enabled' message is logged or the alert is popped up.
AVAudioSession *session = [AVAudioSession sharedInstance];
if ([session respondsToSelector:#selector(requestRecordPermission:)]) {
[session performSelector:#selector(requestRecordPermission:) withObject:^(BOOL granted) {
if (granted) {
// Microphone enabled code
NSLog(#"Microphone is enabled..");
}
else {
// Microphone disabled code
NSLog(#"Microphone is disabled..");
// We're in a background thread here, so jump to main thread to do UI work.
dispatch_async(dispatch_get_main_queue(), ^{
[[[[UIAlertView alloc] initWithTitle:#"Microphone Access Denied"
message:#"This app requires access to your device's Microphone.\n\nPlease enable Microphone access for this app in Settings / Privacy / Microphone"
delegate:nil
cancelButtonTitle:#"Dismiss"
otherButtonTitles:nil] autorelease] show];
});
}
}];
}
EDIT: It turns out that the withObject block is executed in a background thread, so DO NOT do any UI work in there, or your app may hang. I've adjusted the code above. A client pointed this out on what was thankfully a beta release. Apologies for the mistake.
Please note that this will only work if built with Xcode 5, and not with 4.6
Add the AVFoundation Framework to your project
Then import the AVAudioSession header file, from the AVFoundation framework, where you intend to check if the microphone setting is enabled
#import <AVFoundation/AVAudioSession.h>
Then simply call this method
[[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
if (granted) {
// Microphone enabled code
}
else {
// Microphone disabled code
}
}];
The first time this method runs, it will show the prompt to allow microphone access and based on the users response it will execute the completion block. From the second time onwards it will just act based on the stored setting on the device.
Swift answer:
if AVAudioSession.sharedInstance().recordPermission() == .Denied {
print("Microphone permission refused");
}
Or you can use framework like PermissionScope which permit to easily check permissions. https://github.com/nickoneill/PermissionScope
Edit: Swift 3 answer:
import AVFoundation
...
if AVAudioSession.sharedInstance().recordPermission() == .denied {
print("Microphone permission refused");
}
I'm not 100% certain if we're allowed to talk about iOS 7 outside of Apple's devforums, but I found the answer you're looking for there.
In short, you'll find your solution in the AVAudioSession.h header file in the SDK. And if you want to make use of it while still supporting iOS 6, make certain to use "respondsToSelector:" to check for the API availability.

Network communication in main UI thread

I have following concern..
My application downloads 6 MB of data when it is started for the first time.
During that process UIView with information about ongoing download is presented, and generally there is no interaction with user.
Because of that I do all downloading of data in main UI Thread, using dispatch_async, but now I don't know if this is the best solution and what will Apple say when I will submit my application.
Could you please guide my if this is Ok or not ?
Update
Dispatch code
//Called at the end of [UIViewController viewDidLoad]
-(void)splashScreenAppeared
{
myTabBarController_.loadingLabel.text = NSLocalizedString(#"Checking for updates",#"launch progress");
dispatch_queue_t d_queue = dispatch_get_main_queue();
[self launchActionCheckIfDataIsStored:d_queue];
}
//...
-(void)launchActionCheckIfDataIsStored:(dispatch_queue_t)queue
{
dispatch_async(queue, ^ {
//If there is no data stored in core data then download xml data files and images
if (![self isAnyDataStoredInCoreData]) {
launchNoDataStored_ = YES;
[self launchDownloadData:queue];
} else {
launchNoDataStored_ = NO;
[self launchCheckNewVersion:queue];
}
});
}
//...
-(void)launchDownloadData:(dispatch_queue_t)queue
{
myTabBarController_.loadingLabel.text = NSLocalizedString(#"Downloading catalog data",#"launch progress");
dispatch_async(queue, ^ {
[self loadMenuData];
if (seriousError_) {
[self launchSeriousError:queue];
return;
}
myTabBarController_.loadingLabel.text = NSLocalizedString(#"Downloading products details",#"launch progress");
dispatch_async(queue, ^ {
[self loadProductsData];
if (seriousError_) {
[self launchSeriousError:queue];
return;
}
//...
//And so on with other parts of download
}
Just to set the background, I have written & submitted 2 successful apps to the App Store, so you know that I am not hypothesizing.
Coming back, you can push the download to a background thread and update the progress in the form of a slider (for the benefit of the user). But if in this scenario the user does not get to do anything anyway then there is no point.
Plus I dont think Apple would reject your app just 'coz of this. One of the main things they check is if you are using any Private API's or something more basic like app crashes. So go ahead and submit for APP store review...
Now I know that network communication in UI Thread can be a good reason to reject your application by Apple.
Consider the following scenario:
Application is for iPhone.
There is no interaction with user.
Application is run on iPad!!
Even though there is no interaction with user, application does not react when user presses 2x button on iPad.
So network communication in UI thread works, but if application is not for iPad only, then application will be rejected in Apple review process.

Why does iOS not inform my application about audio session interruptions?

I am using AVPlayer to play sound from different sources (including iPod music library). Due to the fact that AVPlayer is more low level AVAudioPlayer I have to handle interruptions myself. Using AVAudioPlayer is not an option!
In the Apple developer documents they mention to either listen AVAudioSessionInterruptionNotificationor setup a listener with AudioSessionInitialize. But when doing so I only receive notifications when the interruption ended, but due to their documents my app should be able to handle both.
I am using the following code to initialize my audio session: (simplified version, removed unimportant lines)
AudioSessionInitialize(NULL, NULL, ARInterruptionListenerCallback, nil);
UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(sessionCategory), &sessionCategory);
AudioSessionSetActive(true);
And here is how the listener looks like:
void ARInterruptionListenerCallback(void *inUserData, UInt32 interruptionState) {
if (interruptionState == kAudioSessionBeginInterruption) {
// Here is the code which is never called...
}
else if (interruptionState == kAudioSessionEndInterruption) {
// But this case will be executed!
}
}
Btw. I am expecting this code executed when there is an interruption like a phone call or similar. Am I misunderstand what Apple declares as interruption?
There is a bug in iOS 6. The interrupt code never seems to be called for begin interrupt - That includes both the old deprecated method (using AVAudioSession delegate) and the new notification method.
Myself and a few others have raised a high severity bug request with Apple so I suggest you do the same (bugreport.apple.com) to get their attention.
I'm not sure what you're using the interrupt for, but I was using it to allow audio to resume after the interrupt if necessary, and also update the play/pause button in the interface. I had to change my method to watch the AVPlayer rate property to toggle the play/pause button, and not allow audio to resume after an interruption.
Edit: see this thread