How to handle remote notifications when the app is not running - ios

If my app is running on foreground or background this working fine. I am receiving the notifications and save it on the local database. But if the app killed from the background it receives the remote notifications but the following method is not called. And the issue is if I tap any one of the notification,only that notification will saved on the local database.
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
{
[PFPush handlePush:userInfo];
NSLog(#"Received notification: %#", userInfo);
NSString *alertString = [[userInfo objectForKey:#"aps"]valueForKey:#"alert"];
NSLog(#"%#",alertString);
NSString *msgType = [userInfo objectForKey:#"messageType"];
NSString *senderId = [userInfo objectForKey:#"senderId"];
NSString *receverId = [userInfo objectForKey:#"receverId"];
NSString *msg = [userInfo objectForKey:#"message"];
NSString *timeStr = [userInfo objectForKey:#"Time"];
NSLog(#"msg type%# senderId %# receverId %# message %#",msgType,senderId,receverId,msg);
if ([AppDelegate isNetworkReachable]){
if ([msgType isEqualToString:#"CHAT"]) {
Chatmessage *Cmsg=[[Chatmessage alloc]init];
Cmsg.chat_date =timeStr;
Cmsg.chat_image =#"";
Cmsg.chat_message = msg;
Cmsg.chat_Receiver_Id = receverId;
Cmsg.chat_Sender_Id = senderId;
NSLog(#"recid%#",Cmsg.chat_Receiver_Id);
NSMutableArray *arryMsg = [[NSMutableArray alloc]init];
arryMsg = [[DBModelNew database]getChatMessageBasedOnTime:receverId SenId:senderId time_stamp:timeStr message:msg];
if (arryMsg.count == 0) {
[[DBModelNew database]insertmsg:Cmsg];
}
[[NSNotificationCenter defaultCenter]postNotificationName:#"receivedmessage" object:nil];
chatHistory *chatObj = [[chatHistory alloc]init];
chatObj.chat_meta_id = [NSString stringWithFormat:#"%#",senderId];
chatObj.last_send_message = msg;
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"yyyy-MM-dd HH:mm:ss";
NSString *str=[dateFormatter stringFromDate:[NSDate date]];
chatObj.last_time_stamp = [self dateformat:str];
PFQuery *query = [PFUser query];
[query whereKey:#"objectId" equalTo:senderId];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (NSDictionary *dict in objects) {
[[dict objectForKey:#"ProfilePic"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
if (data) {
UIImage *image = [UIImage imageWithData:data];
if (image) {
chatObj.fndimage = image;
chatObj.name = [dict objectForKey:#"name"];
[[DBModelNew database]insertChat:chatObj];
[[NSNotificationCenter defaultCenter]postNotificationName:#"receivedNewMessage" object:nil];
}
}
}
}];
}
}
}];
}
}
}

From the Apple docs, if the user hard closes the app it does not call the method.
In addition, if you enabled the remote notifications background mode,
the system launches your app (or wakes it from the suspended state)
and puts it in the background state when a remote notification
arrives. However, the system does not automatically launch your app if
the user has force-quit it. In that situation, the user must relaunch
your app or restart the device before the system attempts to launch
your app automatically again.

If you want to launch specific payload dictionary from viewDidLoad then you simply call the following :
UILocalNotification *localNotif =
[launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
And you get the userInfo this way:
NSString *msgType = [localNotif objectForKey:#"messageType"];
And now you can act accordingly. This is just for the circumstances you stated in your title. When the app is not 'running' (terminated)

Related

iOS network connection on notification

The following code generally works but throws an SSL handshake failed (-9806) when the device is locked.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
{
NSString *roomID = [userInfo objectForKey:#"roomId"];
if (roomID) {
//tell firebase to update this conversation
[self.fb updateChatRoomMessages:roomID withBlock:^(BOOL success) {
/*code gets to here as I can see that with breakpoints,
but before we get here we can see the SSL handshake
error in console (only when phone is locked)*/
handler(UIBackgroundFetchResultNewData);
}];
} else {
handler(UIBackgroundFetchResultNoData);
}
}
Now basically updateChatRoomMessages tries to talk to firebase but I assume the problem is with just about any network connection. Is there known any restriction of the sort?
Any ideas?
Update - rest of the code
(void)updateChatRoomMessages:(NSString *)roomID withBlock:(void (^)(BOOL))completionBlock{
ChatRoomSummary *room = [[DataCollections shared] getChatRoomById:roomID];
Firebase *ref = [[Firebase alloc] initWithUrl:[NSString stringWithFormat:#"%#/chatdata/messages/%#",
self.baseURL, roomID]];
[ref observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *allMsgs) {
dispatch_async(dispatch_get_main_queue(), ^{
[room.messages removeAllObjects]; //clearing the list of messages so that we update it
NSDictionary *dict = allMsgs.value;
for(NSString *snapshot in [dict allKeys]) {
NSDictionary *currentSnapshot = [dict objectForKey:snapshot];
[currentSnapshot setValue:snapshot forKey:#"messageID"];
[[DataEventListener shared] onNewMessage:currentSnapshot forRoom:room preventNotification:YES];
}
[Utility notify:NOTIFY_NEW_ROOM];
[self updateBadges:nil];
if (completionBlock) {
completionBlock(YES);
}
});
}];
}

Push Notifications doesn't always trigger methods in iOS7

I'm receiving notifications using this method:
- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"reloadComments" object:nil];
}
That trigger this method:
- (void) reloadComments:(NSNotification *)notification{
NSDictionary* dict = [notification.userInfo objectForKey:#"commentNotification"];
NSString* video_id = [[[dict objectForKey:#"aps"] objectForKey:#"custom"] objectForKey:#"data"];
NSData* cData = [video_id dataUsingEncoding:NSUTF8StringEncoding];
NSError *errorJson2;
NSMutableDictionary *response = [NSJSONSerialization JSONObjectWithData:cData options:kNilOptions error:&errorJson2];
int number = [[commentsDictionary objectForKey:[NSString stringWithFormat:#"%#", [response objectForKey:#"video_id"]]] intValue];
number += 1;
[commentsDictionary setObject:[NSNumber numberWithInt:number] forKey:[NSString stringWithFormat:#"%#", [response objectForKey:#"video_id"]]];
}
I'm parsing the result and incrementing the number. This works correctly when i launch the app, in my device, through XCode. If i send 5 push notifications the number is 5.
If i do the same procedure without launching the app through XCode, the number is not correctly incremented.
Anyone has any experience with this and can point me in the right direction?
I added the completionHandler below and it started working.
- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
[self parsePushNotifications:userInfo];
completionHandler(UIBackgroundFetchResultNewData);
}

Running code in the background in IOS

Firebase * ref = nil;
NSInteger iid = [[API sharedInstance] userid];
NSString * path = [NSString stringWithFormat: #"http://example.firebaseIO.com/user/%d/conversations", iid];
ref = [[Firebase alloc] initWithUrl:path];
if(ref) {
NSString * path = [NSString stringWithFormat: #"http://example.firebaseIO.com/conversations"];
Firebase * conv = [[Firebase alloc] initWithUrl: path];
[ref observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
// name of conversation
NSString * name = snapshot.name;
Firebase * ref1 = [conv childByAppendingPath: name];
[ref1 observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
if(snapshot.value != [NSNull null] && ![snapshot.value isKindOfClass: [NSString class]])
{
FDataSnapshot * chatsnapshot = [snapshot childSnapshotForPath: #"chats"];
NSInteger numChatMessages = chatsnapshot.childrenCount;
numberOfTotalChatMessages += numChatMessages;
NSMutableDictionary *m = [snapshot.value mutableCopy];
[m setValue: snapshot.name forKey: #"ref_name"];
NSInteger current_user = [[API sharedInstance] userid];
NSString * userpath = [NSString stringWithFormat: #"users/%d", current_user];
FDataSnapshot * usersnapshot = [snapshot childSnapshotForPath: userpath];
if(usersnapshot.value != [NSNull null] && ![usersnapshot.value isKindOfClass: [NSString class]])
{
NSDictionary * userdict = usersnapshot.value;
NSInteger numUserMessagesRead = [userdict[#"numOfMessages"] intValue];
numberOfMessagesRead += numUserMessagesRead;
if(numberOfTotalChatMessages > numberOfMessagesRead) {
[m setValue: #"true" forKey: #"bubble"];
}
}
[self.chats addObject: m];
NSNumber * index = [NSNumber numberWithInt: self.chats.count - 1];
[read setValue: index forKey: snapshot.name];
PLRightMenuViewController * rightPanel = (PLRightMenuViewController *) self.viewController.rightPanel;
[rightPanel.tableView reloadData];
self.numChats = numberOfTotalChatMessages - numberOfMessagesRead;
[[UIApplication sharedApplication] setApplicationIconBadgeNumber: self.numChats];
}
}];
}];
[ref observeEventType:FEventTypeChildChanged withBlock:^(FDataSnapshot *snapshot) {
NSString * name = snapshot.name;
Firebase * ref1 = [conv childByAppendingPath: name];
[ref1 observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot)
{
if(snapshot.value != [NSNull null] && ![snapshot.value isKindOfClass: [NSString class]])
{
numberOfTotalChatMessages += 1;
NSMutableDictionary *m = [snapshot.value mutableCopy];
[m setValue: snapshot.name forKey: #"ref_name"];
[m setValue: #"true" forKey: #"bubble"];
[self.chats addObject: m];
if([read objectForKey: snapshot.name])
{
NSInteger index = [[read objectForKey: snapshot.name] intValue];
[self.chats removeObjectAtIndex: index];
NSNumber * index1 = [NSNumber numberWithInt: self.chats.count - 1];
[read setValue: index1 forKey: snapshot.name];
}
self.numChats = numberOfTotalChatMessages - numberOfMessagesRead;
[[UIApplication sharedApplication] setApplicationIconBadgeNumber: self.numChats];
PLRightMenuViewController * rightPanel = (PLRightMenuViewController *) self.viewController.rightPanel;
[rightPanel.tableView reloadData];
}
}];
}];
}
I have the code above that basically checks for any new chat conversations using firebase and changes the application badge number. How can I run the code in the background of the app so that the application badge number is changed regardless of whether someone is currently using the app or not?
Basically, how can I run the code above in the background? What should I change in the Appdelegate?
You can't unless you cheat. Currently iOS or Apple respectively does not allow apps to go into the background with very few exceptions. Such as location services or playing audio.
Some cheat by pretending to play a sound or so.
Until now you would have to use push notifications in order to inform the app about incoming messages and update the badge.
Or ... wait for iOS 7 to be released. Assuming you've got a developer account, you can already access the docs and preview/beta resouces and prepare yourself until iOS 7 and the SDK etc. is GA.
// #interface
// Declare Private property
#property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
//#end
// ...
// Copy into
//#implementation
- (void)setupBackgrounding {
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(appBackgrounding:)
name: UIApplicationDidEnterBackgroundNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(appForegrounding:)
name: UIApplicationWillEnterForegroundNotification
object: nil];
}
- (void)appBackgrounding: (NSNotification *)notification {
[self keepAlive];
}
- (void) keepAlive {
self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
[self keepAlive];
}];
}
- (void)appForegrounding: (NSNotification *)notification {
if (self.backgroundTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
}
}
You can do it with Push notification here is a great explanation about it.
example http://www.raywenderlich.com/32960/apple-push-notification-services-in-ios-6-tutorial-part-1
Note: you need one APNS which will notify your application.
In iOS7 come with feature with fetchAPI which will allow you to work on background but please note it not grantee that your application will run on background as system will decide when to allow application to run on background.
In iOS7 there is also one more thing call silent-Push notification which will allow you to update your view while notification come it means if your application in background and notification come you can change application badge number in background.
You need to use Apple Push Notification services, commonly abbreviated as APNs.
You can use these methods, to run background task for some more time..
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^)(void))handler NS_AVAILABLE_IOS(4_0);
- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)identifier NS_AVAILABLE_IOS(4_0);
- (BOOL)setKeepAliveTimeout:(NSTimeInterval)timeout handler:(void(^)(void))keepAliveHandler NS_AVAILABLE_IOS(4_0);
- (void)clearKeepAliveTimeout NS_AVAILABLE_IOS(4_0);
- (void)getDataFromServer
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self beginBackgroundUpdateTask];
NSURLResponse * response = nil;
NSError * error = nil;
NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];
// Do something with the result
[self endBackgroundUpdateTask];
});
}
- (void) beginBackgroundUpdateTask
{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void) endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
Further more if you want to keep alive your application you can use this:-
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store
// enough application state information to restore your application to its current state in case
// it is terminated later.
//
// If your application supports background execution,
// called instead of applicationWillTerminate: when the user quits.
if ([application respondsToSelector:#selector(setKeepAliveTimeout:handler:)])
{
[application setKeepAliveTimeout:600 handler:^{
DDLogVerbose(#"KeepAliveHandler");
// Do other keep alive stuff here.
}];
}
}
It might help you..
And in iOS 7 there may be better ways to do that...

Enable push notifications via a UISwitch

I want to use a UISwitch to enable/disable push notifications. Like in Tweetbot.
Does anyone know how to trigger that?
You can also do it in the following way.
create a IBOutlet for UISwitch
#property (strong, nonatomic) IBOutlet *pushNotificationSwitch;
and in Action method, store the value in NSUserDefaults.
- (IBAction)pushNotificationSwitchChanged:(id)sender
{
NSNumber *switch_value = [NSNumber numberWithBool:[self.pushNotificationSwitch isOn]];
[[NSUserDefaults standardUserDefaults] setObject:switch_value forKey:RECIEVE_APNS];
[[NSUserDefaults standardUserDefaults] synchronize];
}
and check it in viewdidload.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSNumber *sett = [[NSUserDefaults standardUserDefaults] valueForKey:RECIEVE_APNS];
if( [sett boolValue] )
{
[self.pushNotificationSwitch setOn:YES];
}
else{
[self.pushNotificationSwitch setOn:NO];
}
}
and In AppDelegate.m, add the following code
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSNumber *sett = [[NSUserDefaults standardUserDefaults] objectForKey:RECIEVE_APNS];
if( [sett boolValue] )
{
int currentBadgeCount = [[NSUserDefaults standardUserDefaults] integerForKey:#"BadgeCount"];
//Set the baadge count on the app icon in the home screen
int badgeValue = [[[userInfo valueForKey:#"aps"] valueForKey:#"badge"] intValue];
[UIApplication sharedApplication].applicationIconBadgeNumber = badgeValue + currentBadgeCount;
[[NSUserDefaults standardUserDefaults] setInteger:badgeValue + currentBadgeCount forKey:#"BadgeCount"];
NSString *alertString = [[userInfo objectForKey:#"aps"] objectForKey:#"alert"];
NSString *playSoundOnAlert = [NSString stringWithFormat:#"%#", [[userInfo objectForKey:#"aps"] objectForKey:#"sound"]];
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/%#",[[NSBundle mainBundle] resourcePath],playSoundOnAlert]];
NSError *error;
if (alertString.length > 0)
{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"App Name" message:alertString delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops = 1;
[audioPlayer play];
[alert show];
}
}
}
enter code here
You can not do that directly from the application. If you want to do this, you need to make the UISwitch send the information to your backend, store this information in your database and stop sending push notifications to this user.
An app registers for Push Notifications (APN) when it first launches. You cannot have it initialize APNs with a switch once it has already launched. You can however code your app that a switch can choose to do "something" with the user interface once a APN is received.
For example, you can have this code:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSDictionary *apsInfo = [userInfo objectForKey:#"aps"];
NSString *alert = [apsInfo objectForKey:#"alert"];
// do what you need with the data...
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReceivedNotificationAlert" object:self];
}
You can use your UISwitch to either do something, or not, with the NSNotification "ReceivedNotificationAlert". For example:
if(switchAPNprocess.on){
// process APN
}
else {
// ignore APN
}

iCloud Save data With UIDocument crash

I am trying to use iCloud to store my app's userSetting, here is my Save & Load Code: , it usually doing fine but sometimes crash with message like: attempt to open or a revert document that already has an open or revert operation in flight or send to dealloc instance so i add fileState logs befere openWithCompletionHandler it always show state = UIDocumentStateClosed no matter will crash or not, i save data when applecationDidEnterBackground and load when applicationDidBecomeActive.
save:
-(void)storeToiCloud{
NSURL *baseURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (baseURL) {
NSURL *documentsURL = [baseURL URLByAppendingPathComponent:#"Documents"];
NSURL *documentURL = [documentsURL URLByAppendingPathComponent:[NSString stringWithFormat:#"userSetting"]];
if (!loadDocument) {
self.loadDocument = [[MyUserDefaultsDocument alloc] initWithFileURL:documentURL];
}
loadDocument.myUserDefault = [MyUserDefaults standardUserDefaults];
[loadDocument saveToURL:documentURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
}];
}
}
load:
-(BOOL)shouldSynciCloud{
if (![Utility iCloudEnable]) {
return NO;
}
NSURL *baseURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (baseURL) {
self.query = [[[NSMetadataQuery alloc] init] autorelease];
[self.query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K == 'userSetting'", NSMetadataItemFSNameKey];
[self.query setPredicate:predicate];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(queryDidFinish:) name:NSMetadataQueryDidFinishGatheringNotification object:self.query];
[self.query startQuery];
[Utility showSpinner];
return YES;
}
return NO;
}
- (void)queryDidFinish:(NSNotification *)notification {
NSMetadataQuery *query = [notification object];
// Stop Updates
[query disableUpdates];
// Stop Query
[query stopQuery];
[query.results enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSURL *documentURL = [(NSMetadataItem *)obj valueForAttribute:NSMetadataItemURLKey];
if([[documentURL lastPathComponent] hasPrefix:#"userSetting"]){
self.document = [[MyUserDefaultsDocument alloc] initWithFileURL:documentURL];
NSString* message;
if (document.documentState == UIDocumentStateNormal){
message = #"UIDocumentStateNormal";
}else if (document.documentState == UIDocumentStateClosed) {
message = #"UIDocumentStateClosed";
}else if(document.documentState == UIDocumentStateEditingDisabled){
message = #"UIDocumentStateEditingDisabled";
}else if(document.documentState == UIDocumentStateInConflict){
message = #"UIDocumentStateInConflict";
}else if(document.documentState == UIDocumentStateSavingError){
message = #"UIDocumentStateSavingError";
}
NSLog(#"state = %#",message);
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
MyUserDefaults *prefs = [MyUserDefaults standardUserDefaults];
NSData *book =[document.myUserDefault.realDict objectForKey:#"realbook"];
NSData *readSetting = [document.myUserDefault.realDict objectForKey:#"epubRS"];
if (book&&[[NSUserDefaults standardUserDefaults] boolForKey:#"iCloudBook"]) {
[prefs setObject:book forKey:#"realbook"];
[Utility reloadRealBooks];
}
if (readSetting&&[[NSUserDefaults standardUserDefaults] boolForKey:#"iCloudSetting"]) {
[prefs setObject:readSetting forKey:#"epubRS"];
[Utility setEpubReadSettingFromData:readSetting];
}
[prefs save];
[[NSNotificationCenter defaultCenter]postNotificationName:#"iCloudSynced" object:nil];
[Utility removeSpinner];
}
else{
[[NSNotificationCenter defaultCenter]postNotificationName:#"iCloudSyncfailed" object:nil];
[Utility removeSpinner];
}
}];
}
}];
if ([query.results count]==0) {
[[NSNotificationCenter defaultCenter]postNotificationName:#"iCloudSyncfailed" object:nil];
[Utility removeSpinner];
}
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:nil];
}
As noted in this question, the error occurs if your app attempts to call [document openWithCompletionHandler:] method twice in close succession.
Because the openWithCompletionHandler: opens the document asynchronously, the document may still be opening when the method is called again.
If this happens, your app ends up trying to open the document twice (as the document state will remain UIDocumentStateClosed until completion) and this causes the exception to be thrown.

Resources