I just upgraded my whole iOS push notifications registering for iOS 10, with this code:
-(void)registerForNotifications{
if(SYSTEM_VERSION_GRATERTHAN_OR_EQUALTO(#"10.0")){
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error){
dispatch_async(dispatch_get_main_queue(), ^(void){
if(!error){
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
});
}];
}
else {
if ([[UIApplication sharedApplication] respondsToSelector:#selector(registerUserNotificationSettings:)])
{
UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
}
}
All my delegates are set in my AppDelegate.
EDIT: I have now been able to further identify the issue. When the app comes back in foreground after notification push, the delegate:
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
is only called after about 10-15 seconds, while normally it is obviously called immediately. How is this possible?
I'm now testing push notifications with Localytics and I implement:
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
in my AppDelegate for deep linking purposes. When I add breakpoints, I see that this happens:
I receive the push notification correctly, I tap on it, and the app UI freezes for about 10 seconds. Then, finally, didReceiveNotificationResponse is called and the deep linking works.
How can I avoid this huge delay which freeezes the app?
EDIT: it's even worse than I though. When I connect my iPhone to xCode and run a build on my phone, it freezes for ten seconds before working. Then, if I just run the exact same build without running it on xCode (so without breakpoints), the app freezes for 10 seconds and then crashes.
EDIT: here is a screenshot of my main thread when I pause on xCode while it freezes:
There's something weird in your stacktrace. semaphore_wait_trap, semaphore_wait_slow. Try seeing here and here and here. That being said my guess is that you're calling your (void)registerForNotifications from a wrong thread and it's causing this issue.
Additionally I don't understand why you have a dispatch_async(dispatch_get_main_queue in your implementation. It seems unnecessary. Try seeing answers from here
Related
I have developed an app for both Android and IOS to receive push notifications, using phone gap push plugin. when I deploy app on Android device, I'm able to receive push notifications. But when I deploy app on IOS and run app for the first time. I'm not getting the popup "Allow push notification", which will give permission for the app to receive push notification. I want to know if anybody has faced this problem earlier or any ideas to fix this issue.
I followed several posts online, but couldn't find any thing related to this. As per my understand popup should be displayed by plugin by default.
Thanks in Advance.
If you use Objective-C, use below code:
// AppDelegate.m
#import UserNotifications;
After that, in method "didFinishLaunchingWithOptions" add this code:
if( SYSTEM_VERSION_LESS_THAN( #"10.0" ) )
{
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
[[UIApplication sharedApplication] registerForRemoteNotifications];
if( optind != nil )
{
NSLog( #"registerForPushWithOptions:" );
}
}
else
{
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error)
{
if( !error )
{
NSLog( #"Push registration success." );
}
else
{
NSLog(#"Something went wrong");
}
}];
}
If you use swift, read this https://useyourloaf.com/blog/local-notifications-with-ios-10/ . I think it helpful for you.
all.
I want to ask you about push notification settings.
Environments:
MacOS[High Sierra10.13.3]
Xcode9.2(9C40b)
Devices
- iPhone6S[iOS10.3.3]
- iPhoneX [iOS11.2.6]
Language:
Cocos2d-x (Objective-c)
This problem occurs suddenly, and above methods are called before,
so basic settings must have been correctly done such as "Capabilities", "Provisioning Profiles".
And of course, network is connected to wifi.
So now, here is my code snippet.
- (BOOL)application: (UIApplication*)application didFinishLaunchingWithOptions: (NSDictionary*)launchOptions {
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) {
[[UIApplication sharedApplication] registerForRemoteNotifications];
}];
}
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
[application registerForRemoteNotifications];
}
Also, I try this one.
- (BOOL)application: (UIApplication*)application didFinishLaunchingWithOptions: (NSDictionary*)launchOptions {
UIUserNotificationSettings* setting = [UIUserNotificationSettings settingsForTypes: (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert) categories: nil];
[[UIApplication sharedApplication] registerUserNotificationSettings: setting];
}
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
[application registerForRemoteNotifications];
}
I can see push notification dialog when app is launched, push allow or deny button, requestAuthorizationWithOptions callback is invoked, but nothing happens.
I repeatedly uninstall and install app, but nothing changed.
Added(14/03/2018 18:53)
I create new test project and use the same bundle identifier and Provisioning Profiles.
Then, didRegisterForRemoteNotificationsWithDeviceToken is invoked correctly.
So, maybe configuration of Provisioning Profiles and AppID are ok, my project's setting is incorrect...
If the project is targeting iOS version 10.0+, Apple recommends utilizing the new method for asking for notification permissions:
requestAuthorizationWithOptions:completionHandler:
https://developer.apple.com/documentation/usernotifications/unusernotificationcenter/1649527-requestauthorizationwithoptions?language=objc
I am adding two action buttons to my push notifications on iOS 8: an Accept button and a Deny button. Neither button will open the app, but different server requests will be made depending on which button is pressed. Here's my setup:
+ (void)requestForPushNotificationToken {
UIApplication *application = [UIApplication sharedApplication];
// if ios 8 or greater
if ([application respondsToSelector:#selector(registerUserNotificationSettings:)]) {
UIMutableUserNotificationAction *acceptAction = [[UIMutableUserNotificationAction alloc] init];
[acceptAction setActivationMode:UIUserNotificationActivationModeBackground];
[acceptAction setTitle:#"Accept"];
[acceptAction setIdentifier:#"ACCEPT_ACTION"];
[acceptAction setDestructive:NO];
[acceptAction setAuthenticationRequired:NO];
UIMutableUserNotificationAction *denyAction = [[UIMutableUserNotificationAction alloc] init];
[denyAction setActivationMode:UIUserNotificationActivationModeBackground];
[denyAction setTitle:#"Deny"];
[denyAction setIdentifier:#"DENY_ACTION"];
[denyAction setDestructive:NO];
[denyAction setAuthenticationRequired:NO];
UIMutableUserNotificationCategory *actionCategory = [[UIMutableUserNotificationCategory alloc] init];
[actionCategory setIdentifier:#"ACTIONABLE"];
[actionCategory setActions:#[acceptAction, denyAction]
forContext:UIUserNotificationActionContextDefault];
NSSet *categories = [NSSet setWithObject:actionCategory];
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert) categories:categories];
[application registerUserNotificationSettings:settings];
} else if ([application respondsToSelector:#selector(registerForRemoteNotificationTypes:)]) { // ios 7 or lesser
UIRemoteNotificationType myTypes = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound;
[application registerForRemoteNotificationTypes:myTypes];
}
}
Then, in my delegate method, I am specifying actions to be taken when user pressed one of the action buttons:
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler {
if ([identifier isEqualToString:#"ACCEPT_ACTION"]) {
// Sending a request to the server here
}
else if ([identifier isEqualToString:#"DENY_ACTION"]) {
// Sending a request to the server here
}
if (completionHandler) {
completionHandler();
}
}
The ideal scenario is that the user does not need to launch the app in the whole process; pressing Accept or Deny will make different calls to the server. With the code above, I am seeing very unstable behaviors with the button actions:
A lot of times, the code in the action handler doesn't execute when app is in background and no server calls are made at all; when this happens, if I tap on my app icon and launch my app, the handler code will immediately be run upon launching the app.
Occasionally, the handler code gets triggered and everything works fine. Server requests are made as soon as I press one of the action buttons.
If I put breakpoints in my Xcode and step through the handler code, the success rate is also 100%. I do not need to launch my app, and handler code gets executed when button is pressed.
Could anyone please help me figure out what's causing such unstable behavior? Thanks in advance.
I have finally figured out the reason. The fact that it sometimes works and sometimes doesn't should have given me the hint much sooner.
According to the Apple documentation of application:handleActionWithIdentifier:forRemoteNotification:completionHandler::
Your implementation of this method should perform the action associated with the specified identifier and execute the block in the completionHandler parameter as soon as you are done. Failure to execute the completion handler block at the end of your implementation will cause your app to be terminated.
I am calling the completion handler at the end of the application:handleActionWithIdentifier:forRemoteNotification:completionHandler method. However, the fact that I am sending requests to the server in my handler code means that my end of implementation is not simply at the end of the method; my real end lies within the callback of my requests. The way I code it, completion handler and callback are on two different threads, and when completion handler runs before it reaches callback, it'll fail.
So the solution is to move the completion handler into the callback methods of the request, i.e., the real "end of the implementation". Something like this:
[MyClient sendRequest:userInfo withSuccessBlock:^(id responseObject){
NSLog(#"Accept - Success");
if (completionHandler) {
completionHandler();
}
} withFailureBlock:^(NSError *error, NSString *responseString) {
NSLog(#"Accept - Failure: %#",[error description]);
if (completionHandler) {
completionHandler();
}
}];
On iOS8 you need to call
[application registerForRemoteNotifications];
from application:didRegisterUserNotificationSettings::
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
[application registerForRemoteNotifications];
}
I have seen too many questions about the silent push notification does not work if the device is not connected to xcode, but I could not find the real answer.
I'm using Silent APN to start a process in background and then fire a local Push notification
Server sends this info:
"_metadata" = {
bagde = 1;
pushText = "semeone has sent you a message!!";
sender = "semeone";
};
aps = {
"content-available" = 1;
};
And _metadata is customized info to fire the local notification, I did not included badge, pushText.. in aps because I it is a silent push notification.
Client should get the info in didReceiveRemoteNotification,
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
if(application.applicationState != UIApplicationStateActive ){
if([userInfo[#"aps"][#"content-available"] intValue]== 1) //it's the silent notification
{
//start a background task
UIBackgroundTaskIdentifier preLoadPNTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Background task to start a process ");
}];
//end completionHandler regarding to fetchCompletionHandler
completionHandler(UIBackgroundFetchResultNewData);
// doing my process...... and fire a local notification
if(preLoadPNTask){
NSLog(#"End Background task ");
[[UIApplication sharedApplication] endBackgroundTask:preLoadPNTask];
preLoadPNTask = 0;
}
return;
}
else
{
NSLog(#"didReceiveRemoteNotification it's NOT the silent notification ");
completionHandler(UIBackgroundFetchResultNoData);
return;
}
}
else {
if(preLoadPNTask){
NSLog(#"End Background task ");
[[UIApplication sharedApplication] endBackgroundTask:preLoadPNTask];
preLoadPNTask = 0;
}
completionHandler(UIBackgroundFetchResultNewData);
}
}
It works perfectly fine when the device is connecting to xcode, but when it doesn't, the didReceiveRemoteNotification doesn't start :(
Any ideas?
Thank you in advance!!
What I end up is a cable USB was cause me some issues apparently every time that I plugged in the iphone device said that "this accessory may not be supported" but it continue working normally , so I replace for a new one, but that not solve my issue, however can be part of this. so I looked in the code, and I did some changes, after receive 2 o more silent push notification preLoadPNTask (UIBackgroundTaskIdentifier) was creating many times so I added a validation before it start,
if(!preLoadPNTask){
//start a background task
UIBackgroundTaskIdentifier preLoadPNTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Background task to start a process ");
}];
}
I hope this help you
Regards
In ios 8, You need to the following steps to fire didReceiveRemoteNotification: method
Select project target goto Capabilities tab
Select 'Background modes' turn on.
It'll add a key (Required background modes)in your project info.plist
After these modifications, when you get a apple push notification and if the app is in background already then didReceiveRemoteNotification will be fired.
Probably because under iOS 8 you have to ask for push notifications in a different way. Try this:
-(void) registerForPushNotifications {
UIApplication* application=[UIApplication sharedApplication] ;
// Register for Push Notitications, if running iOS 8
if ([application respondsToSelector:#selector(registerUserNotificationSettings:)]) {
UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes categories:nil];
[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];
} else {
// Register for Push Notifications before iOS 8
[application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)];
}
}
I want to implement a custom screen that informs my users why I'm about to ask for push notification permissions. After they press a button in that custom screen I present the iOS push notification permission dialog with [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
I only want to show this custom screen once if the user hasn't already seen the push notification permission dialog. I cannot use [[UIApplication sharedApplication] enabledRemoteNotificationTypes] == UIRemoteNotificationTypeNone as this will also return 'none' if the user decided to not allow push notifications.
Any ideas anyone?
You could use NSUserDefaults :
#define kPushNotificationRequestAlreadySeen #"PushNotificationRequestAlreadySeen"
if(![[NSUserDefaults standardUserDefaults] boolForKey:kPushNotificationRequestAlreadySeen]) {
// Notify the user why you want to have push notifications
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:... delegate:self ...];
[alertView show];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kPushNotificationRequestAlreadySeen];
}
else {
// Already allowed -> register without notifying the user
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound];
}
And
- (void)alertView:(UIAlertView*)alertView didDismissWithButtonIndex:(NSInteger)index {
// Actually registering to push notifications
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound];
}
This is just a work-around but will work in the majority of cases.
When iOS prompts the user to allow push notifications, applicationWillResignActive is called.
After calling registerForRemoteNotificationTypes...start a timer (or dispatch_after) that will show a new alert to the user explaining that they'll need to use the Settings app to enable notifications. Then in applicationWillResignActive cancel the timer. This way, the explanation alert will only show if applicationWillResignActive is never called.
The only problem I see with this is if the app resigns active for other reasons at the exact time that the timer is going...but this puts you in no worse a situation that you're already in and has the advantage of working in the majority of cases.
The solution that I've found is a bit of a hack, but it works.
You need to call registerUserNotificationSettings for two different notificationSettings - one without a notificationCategory and one with a notificationCategory:
//Request notification permission
UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
//Request notification permission again, but with a category with no actions
UIMutableUserNotificationCategory *category = [[UIMutableUserNotificationCategory alloc] init];
category.identifier = #"com.xyz.markNotificationPopupShownCategoryIdentifier";
UIUserNotificationSettings *notificationSettingsWithCategory = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:[NSSet setWithObject:category]];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettingsWithCategory];
The didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings method in the app delegate will be called two times, and irrespective of the user's answer in the permission notification, after the second call, the current notification settings will contain the category. As long as the category count is greater than 0, you can know for sure that the notifications permission dialog has been shown:
if ([UIApplication sharedApplication].currentUserNotificationSettings.categories.count > 0) {
NSLog(#"Notifications permission has been asked");
} else {
NSLog(#"Notifications permission hasn't been asked");
}