I have some problems receiving push notifications under IOS.
the didReceiveRemoteNotification fires but somewhere my code crashesh in the NSNotificationCenter. That's not the problem but basically I even don't receive nothing on the phone. Is the didReceiveRemoteNotification implementation correct ? or I missed something ?
This is what I send:
"aps":[],"custom":["hello IOS","0","48.213822","16.389186","1.39593410094E+12",null]}
And this the source.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
if (isAttentionViewOpen) {
NSLog(#"Sent Notification to view!");
[[NSNotificationCenter defaultCenter] postNotificationName:#"PushIn" object:self userInfo: userInfo];
}
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
//#if !TARGET_IPHONE_SIMULATOR
NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleDisplayName"];
NSString *appVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"];
NSUInteger rntypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
NSString *pushBadge = #"disabled";
NSString *pushAlert = #"disabled";
NSString *pushSound = #"disabled";
if(rntypes == UIRemoteNotificationTypeBadge){
pushBadge = #"enabled";
}
else if(rntypes == UIRemoteNotificationTypeAlert){
pushAlert = #"enabled";
}
else if(rntypes == UIRemoteNotificationTypeSound){
pushSound = #"enabled";
}
else if(rntypes == ( UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert)){
pushBadge = #"enabled";
pushAlert = #"enabled";
}
else if(rntypes == ( UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)){
pushBadge = #"enabled";
pushSound = #"enabled";
}
else if(rntypes == ( UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)){
pushAlert = #"enabled";
pushSound = #"enabled";
}
else if(rntypes == ( UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)){
pushBadge = #"enabled";
pushAlert = #"enabled";
pushSound = #"enabled";
}
// Get the users Device Model, Display Name, Unique ID, Token & Version Number
UIDevice *dev = [UIDevice currentDevice];
CFUUIDRef uuidObject = CFUUIDCreate(kCFAllocatorDefault);
NSString *uuidStr = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuidObject);
CFRelease(uuidObject);
deviceUuid = uuidStr;// dev.uniqueIdentifier;
NSString *deviceName = dev.name;
NSString *deviceModel = dev.model;
NSString *deviceSystemVersion = dev.systemVersion;
// Prepare the Device Token for Registration (remove spaces and < >)
NSString *deviceToken = [[[[devToken description]
stringByReplacingOccurrencesOfString:#"<"withString:#""]
stringByReplacingOccurrencesOfString:#">" withString:#""]
stringByReplacingOccurrencesOfString: #" " withString: #""];
// SENT IT TO SERVER
Since your aps does not hold any data not notification is shown to the user.
The -application:didReceiveRemoteNotification is only called if your app is in the foreground, not when it is in the background.
For this you need to use application:didReceiveRemoteNotification:fetchCompletionHandler:, but this is only available on iOS 7.
If you want to handle push notification in the background then implement the above mentioned method and register that you app want to do remote-notification in the background.
There are many thing to keep in mind when doing this, see the comment in the Apple documentation of application:didReceiveRemoteNotification:fetchCompletionHandler:
Implement this method if your app supports the remote-notification
background mode. This method is intended as a means for apps to
minimize the time that elapses between the user seeing a push
notification and the app displaying the associated data. When a push
notification arrives, the system displays the notification to the user
and launches the app in the background (if needed) so that it can call
this method. Use this method to download any data related to the push
notification. When your method is done, call the block in the handler
parameter.
Unlike the application:didReceiveRemoteNotification: method, which is called only when your app is running, the system calls this method regardless of the state of your app. If your app is suspended or not running, the system wakes up or launches your app and puts it into the background running state before calling the method. If the user opens your app from the system-displayed alert, the system calls this method again so that you know which notification the user selected.
When this method is called, your app has up to 30 seconds of wall-clock time to perform the download operation and call the specified completion handler block. In practice, your app should call the handler block as soon as possible after downloading the needed data. If you do not call the handler in time, your app is terminated. More importantly, the system uses the elapsed time to calculate power usage and data costs for your app’s background downloads.
Related
I'm facing an issue after updating my app. I'm getting a device token in this format
C43461D5-E9CB-4C10-81F8-020327A07A62
and the notifications aren't working.
Before, I had a notification in this format:
2add70865401171c7ca1d3b3957b719eaf721fef8f8d7a52bc91ef8a872cc004
I did allow notifications for the app and I've not changed anything in the backend. Can anyone guide me why it's not working?
Code in didFinishLaunchingWithOptions:
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
{
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
else
{
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert)];
}
Getting the device token:
NSString *deviceTokenID = [[NSUserDefaults standardUserDefaults] objectForKey:DEVICE_TOKEN_PUSH_NOTI];
if ([deviceTokenID isEqualToString:#""] || deviceTokenID == nil) {
NSString *tempApplicationUUID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
[dics setObject:tempApplicationUUID forKey:#"cust_device_id"];
} else {
[dics setObject:[[NSUserDefaults standardUserDefaults] objectForKey:DEVICE_TOKEN_PUSH_NOTI] forKey:#"cust_device_id"];
}
I got the below error:
Code=3000 "no valid 'aps-environment' entitlement string found for
application" UserInfo={NSLocalizedDescription=no valid
'aps-environment' entitlement string found for application}, no valid
'aps-environment' entitlement string found for application
plz go to
Click on .xcodeproj -> Capabilities -> Enable Push Notification
hope it's work
Click on .xcodeproj -> Capabilities -> Enable Push Notification
NSString *tempApplicationUUID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
The UUIDString is the Unique device ID but for the Apple Push Notification you required the device token get back from APNS so for that Use the below method you get 64 digit deviceToken and get notification.
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSString *devToken = [[[[deviceToken description]
stringByReplacingOccurrencesOfString:#"<"withString:#""]
stringByReplacingOccurrencesOfString:#">" withString:#""]
stringByReplacingOccurrencesOfString: #" " withString: #""];
NSString *str = [NSString
stringWithFormat:#"Device Token=%#",devToken];
[[NSUserDefaults standardUserDefaults]setObject:devToken forKey:#"DeviceToken"];
}
I am facing a problem in iOS push notification in a Cordova hybrid app, need guidance/help in identifying whether I am doing it the right way?
Here is the flow ->
User recieved push notification while app is in background -> Tap on the notification from the notification center -> App is opened (invoked) -> Get the custom payload values -> perform a JS Callback using stringByEvaluatingJavaScriptFromString -> The JS function will do the necessary actions the app has to do.
Here is my code where I am doing it
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
[[Pushbots sharedInstance] receivedPush:userInfo]; //Using Pushbots notification platform
NSString* Value1 = [userInfo objectForKey:#"val1"];
NSString* Value2 = [userInfo objectForKey:#"val2"];
NSString * jsCallBack = [NSString stringWithFormat:#"testfromdelegate('%#','%#')", Value1, Value2];
NSLog(#"%#", jsCallBack);
[self.viewController.webView stringByEvaluatingJavaScriptFromString:jsCallBack];
}
Everything works till my NSLog(#"%#", jsCallBack);, and it fails at the jsCallBack. I understood that by the time my jsCallBack is being called, the webView is not yet ready, as the app is invoked from background.
What do I do to make this work? How to check whether my webView is ready, and then perform my jsCallBack?
How do I do this when the app is not running at all? For e.g., a user received a Whatsapp message and the app is not in background/foreground. But when the user taps on the message from the notification center, the whatsapp app opens with the user chat screen.
PS: All this code perfectly works when the app is in foreground. That means, the webView is already there and so jsCallBack has no problem.
If the app is in open or in background, then you can execute the javascript directly:
//This will be called if the app was open, in the foreground (active) or in the background and you reopen it from a push message
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
[[Pushbots sharedInstance] receivedPush:userInfo]; //Using Pushbots notification platform
NSString* Value1 = [userInfo objectForKey:#"val1"];
NSString* Value2 = [userInfo objectForKey:#"val2"];
NSString * jsCallBack = [NSString stringWithFormat:#"testfromdelegate('%#','%#')", Value1, Value2];
NSLog(#"%#", jsCallBack);
// If the app is in the foreground just execute the javascript
if ( application.applicationState == UIApplicationStateActive ) {
[self.viewController.webView stringByEvaluatingJavaScriptFromString: jsCallBack];
} else {
// If the app was in background, then force to execute the js code on the main thread
[self.viewController.webView performSelectorOnMainThread:#selector(stringByEvaluatingJavaScriptFromString:) withObject:jsCallBack waitUntilDone:NO]
}
}
If the app was completelly closed, then you can add an observer that will listen for the CDVPageDidLoadNotification (cordova finish loading), so you store the userInfo in a new variable myUserInfo and wait until the page is loaded to use it:
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
//Don't remove the existing code, just paste this before the return YES
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(cordovaDidLoad) name:CDVPageDidLoadNotification object:self.viewController.webView];
//This will be called if the app was closed completelly and you open it from a push
if (launchOptions) { //launchOptions is not nil
NSDictionary *userInfo = [launchOptions valueForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
self.myUserInfo = userInfo;
}
return YES;
}
Then, when cordova is loaded this method will be called:
- (void)cordovaDidLoad {
//Check that myUserInfo isn't null, that will mean that the apps was completelly closed and it was opened from the push notification
if (self.myUserInfo) {
NSString* Value1 = [self.myUserInfo objectForKey:#"val1"];
NSString* Value2 = [self.myUserInfo objectForKey:#"val2"];
NSString * jsCallBack = [NSString stringWithFormat:#"testfromdelegate('%#','%#')", Value1, Value2];
NSLog(#"%#", jsCallBack);
[self.viewController.webView stringByEvaluatingJavaScriptFromString: jsCallBack];
}
}
Is the a way of knowing if the user declined the push notifications permissions?
I know that didRegisterForRemoteNotificationsWithDeviceToken will be called in case the user allowed push notifications - but what will be called if he didn't?
A simple method to check whether notification is enabled in app or not.
-(BOOL)checkNotificationEnabled
{
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
if (types == UIRemoteNotificationTypeNone)
{
return FALSE; //Notification not enabled
}
else
{
return TRUE;//Notification is enabled
}
}
else // for iOS 8 devices checking will be different
{
return [[UIApplication sharedApplication] isRegisteredForRemoteNotifications];
// if return TRUE then Notification is enabled
// if return False then Notification is not enabled
}
}
According to the UIApplicationDelegate Protocol Reference, application(_:didFailToRegisterForRemoteNotificationsWithError:) is called if an error occurs during the registration process.
After you call the registerForRemoteNotifications method of the UIApplication object, the app calls this method when there is an error in the registration process.
For more information about how to implement remote notifications in your app, see Local and Remote Notification Programming Guide.
You can implement in following way. Suppose that if user allows notification then you can store device token into user default. Now, next time you can check with same user default weather user allowed or not.
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
NSString *aStrToken = [[deviceToken description] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:#"<>"]];
aStrToken = [aStrToken stringByReplacingOccurrencesOfString:#" " withString:#""];
NSString *aStrStoredToken = [_appDelegate validateString:[[NSUserDefaults standardUserDefaults]objectForKey:#"deviceID"] :#""];
if([aStrStoredToken length]==0) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"getDeviceToken" object:nil];
[self setApplicationBadgeNumber:0];
[[NSUserDefaults standardUserDefaults]setObject:aStrToken forKey:#"deviceID"];
[[NSUserDefaults standardUserDefaults]synchronize];
if(![[NSUserDefaults standardUserDefaults]objectForKey:#"setDeviceID"]) {
[[NSUserDefaults standardUserDefaults]setObject:#"0" forKey:#"setDeviceID"];
[[NSUserDefaults standardUserDefaults]synchronize];
} else {
[[NSUserDefaults standardUserDefaults]setObject:#"1" forKey:#"setDeviceID"];
[[NSUserDefaults standardUserDefaults]synchronize];
}
}
}
Can worklight return the device token for Android/iPhone/BB and if so how?
More specifically, I'm not looking for the "device id" but the native device token.
Worklight can return the "device id", but this is different than the device token. For example Worklight: How to get current device ID for Push subscription states how to get the "device id" using the call
WL.Client.getUserInfo("wl_deviceNoProvisioningRealm", "userId");
Unfortunately this returns something different than the device token. When using the native iPhone call like so and comparing it to the WL deviceid it's obvious they are different.
- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSMutableDictionary *results = [NSMutableDictionary dictionary];
NSString *token = [[[[deviceToken description] stringByReplacingOccurrencesOfString:#"<"withString:#""]
stringByReplacingOccurrencesOfString:#">" withString:#""]
stringByReplacingOccurrencesOfString: #" " withString: #""];
[results setValue:token forKey:#"deviceToken"];
#if !TARGET_IPHONE_SIMULATOR
[results setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleDisplayName"] forKey:#"appName"];
[results setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"] forKey:#"appVersion"];
NSUInteger rntypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
// Set the defaults to disabled unless we find otherwise...
NSString *pushBadge = #"disabled";
NSString *pushAlert = #"disabled";
NSString *pushSound = #"disabled";
if(rntypes & UIRemoteNotificationTypeBadge){
pushBadge = #"enabled";
}
if(rntypes & UIRemoteNotificationTypeAlert) {
pushAlert = #"enabled";
}
if(rntypes & UIRemoteNotificationTypeSound) {
pushSound = #"enabled";
}
[results setValue:pushBadge forKey:#"pushBadge"];
[results setValue:pushAlert forKey:#"pushAlert"];
[results setValue:pushSound forKey:#"pushSound"];
// Get the users Device Model, Display Name, Token & Version Number
UIDevice *dev = [UIDevice currentDevice];
[results setValue:dev.name forKey:#"deviceName"];
[results setValue:dev.model forKey:#"deviceModel"];
[results setValue:dev.systemVersion forKey:#"deviceSystemVersion"];
[self successWithMessage:[NSString stringWithFormat:#"%#", token]];
#else
[self successWithMessage:[NSString stringWithFormat:#"%#", #"simulator generated"]];
#endif
}
Moreover, the native device token is needed for a third party notification platform which is outside of worklight and so using worklights messaging system isn't feasible.
You're correct APNs device token and Worklight deviceId is two different things. In case you require APNs device token for using some 3rd party notification platform you can override the didRegisterForRemoteNotificationsWithDeviceToken method in your application delegate thus receiving full control over the device token once it arrives from APNs
My published application makes use of the CFUUID and SSKeychain in order to identify the device (and to keep that ID unchanged even if the app is uninstalled and reinstalled)
I save those device ID in the server, and I recently noticed that some users have several of those IDs for the same real device. The only explanation I see is that the ID is not being saved or loaded from the Keychain and thus the device generates a new one. The strange thing is that it works fine on some other devices running the same iOS version.
Any ideas on what could be going on?
This is my related code in (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
NSString* identifier = #"appName";
NSString* serviceName = #"com.company.appName";
NSString *retrieveuuid = [SSKeychain passwordForService:serviceName account:identifier];
if (retrieveuuid == nil) {
CFUUIDRef theUUID = CFUUIDCreate(NULL);
CFStringRef string = CFUUIDCreateString(NULL, theUUID);
CFRelease(theUUID);
NSString *uuid = (NSString*) string;
[SSKeychain setPassword:uuid forService:serviceName account:identifier];
}
EDIT: My guess is that retrieveuuid == nil is not working as expected for some reason. Later in the app I register for push notifications and send the push token to the server together with this CFUUID that I read with the same exact line [SSKeychain passwordForService:serviceName account:identifier] but when it is sent to the server it is not nil (So I can see several CFUUIDs with the same push token).
EDIT 2 to attach more actual code.
AppDelegate.m
NSString* identifier = #"appName";
NSString* serviceName = #"com.company.appName";
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Creating UUID
NSString *retrieveuuid = [AppDelegate getDeviceId];
if (retrieveuuid == nil) {
CFUUIDRef theUUID = CFUUIDCreate(NULL);
CFStringRef string = CFUUIDCreateString(NULL, theUUID);
CFRelease(theUUID);
NSString *uuid = (NSString*) string;
[SSKeychain setPassword:uuid forService:serviceName account:identifier];
}
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
}
+ (NSString*) getDeviceId {
return [SSKeychain passwordForService:serviceName account:identifier];
}
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
NSString *newToken = [deviceToken description];
newToken = [newToken stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]];
newToken = [newToken stringByReplacingOccurrencesOfString:#" " withString:#""];
_deviceToken = [[NSString alloc] initWithString:newToken];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *user = [prefs objectForKey:#"appNameUsername"];
if(user && ![user isEqualToString:#""]){
RestClient *rest = [[RestClient alloc] init];
rest.delegate = self;
rest.tag = 2;
[rest updateToken:newToken ForUsername:user];
[rest release];
}
}
RestClient.m
-(void) updateToken:(NSString *)token ForUsername:(NSString *)userName{
NSArray* info = [NSArray arrayWithObject: [NSMutableDictionary dictionaryWithObjectsAndKeys:
userName, #"Username",
token, #"TokenNo",
[[UIDevice currentDevice].model hasPrefix:#"iPad"] ? #"iPad" : #"iPhone", #"Device",
[UIDevice currentDevice].systemVersion, #"OSVersion",
[AppDelegate getDeviceId], #"DeviceID",
#"updateToken", #"CMD",
nil]];
[self doAction:info];
}
The doAction method just sends the data to the server and then callback the delegate, that part works fine. I can see on the server the logs of receiving this command:
"JSONCMD":"[
{ "TokenNo" : "f0d3aa21758350333b7e6315c38_EDIT_257c1838f49c43049f8380ec1ff63",
"AppVersion" : "1.0.4",
"Username" : "user#server.com",
"CMD" : "updateToken",
"OSVersion" : "7.0.4",
"DeviceID" : "9B794E11-6EF7-470C-B319-5A9FCCDAFD2B",
"Device" : "iPhone"
}
]
I see 2 possible candidates causing the strange behaviour, the NSStrings in the controller body and the static getDevice method. However, I don't see how this could work in many devices but fail in others.
I've had the same problem and I've found the solution. The Problem occurs with devices where user has the passcode set to unlock the phone. On iOS7 some code can run in the background (push notifications, background fetch) and if you save something in the keychain with standard accessibility type, you cannot read it when the device is locked (and the app is running in the background). Try to set this:
[SSKeychain setAccessibilityType:kSecAttrAccessibleAlways];
before you read/write the keychain.
Hope it helps.
NSString *retrieveuuid = [SSKeychain passwordForService:serviceName
account:identifier];
The retrieveuuid depends with the service, witch is always the same thing, but it depends also with the account. I think your problem is that one user can have multiple accounts in the same device, then your code are generating a UUID for each account.