In my current project I have a push notification. When I tap the app icon I want to get the received notification from the launch options object, but it always returns nil:
NSDictionary *userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
You can't detect that case, because application is not open using push notification (it has been open via app icon).
Try to open application by swiping push notification.
EDIT:
If you want to be invoked for push notification (via background fetch, when your application is not active) you need to ask your backend developer to set "content-available": 1 in push notification.
After that -application:didReceiveRemoteNotification:fetchCompletionHandler: will be invoked (when push-notification arrives), so you can save the payload into a file and then when application will be open, you can read the file and take actions.
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSLog(#"#BACKGROUND FETCH CALLED: %#", userInfo);
// When we get a push, just writing it to file
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"userInfo.plist"];
[userInfo writeToFile:filePath atomically:YES];
completionHandler(UIBackgroundFetchResultNewData);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Checking if application was launched by tapping icon, or push notification
if (!launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"userInfo.plist"];
[[NSFileManager defaultManager] removeItemAtPath:filePath
error:nil];
NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:filePath];
if (info) {
// Launched by tapping icon
// ... your handling here
}
} else {
NSDictionary *info = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
// Launched with swiping
// ... your handling here
}
return YES;
}
Also don't forget to enable "Remote notifications" in "Background Modes"
When you launch the application from a PUSH NOTIFICATION ACTION, [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey] will return you the push notification payload (in dictionary format). When I say push notification action, it means either tapping the push notification from action center or from the push notification alert dialog (Depending on the device settings, push notification delivery mechanism varies).
If you launch the application by tapping the APP ICON, [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey] always returns nil. Because, it hasn't been launched from any kind of push notification.
Related
I am developing an apple watch application which records an audio file, saves it and then transfers the file URL to the iPhone app via WCSession (Watch Connectivity framework). My code looks like this
In InterfaceController.m
NSURL *directory = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.name.watchtest"];
__block NSString *recordingName = #"myTestFile.mp4";
__block NSURL * outputURL = [directory URLByAppendingPathComponent:recordingName];
if ([WCSession isSupported]) {
if ([self.watchSession isReachable]) {
[self.watchSession transferFile:outputURL metadata:nil];
}
}
In ViewController.m (WCSession delegate)
-(void)session:(WCSession *)session didReceiveFile:(WCSessionFile *)file
{
NSError *error;
NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *docsDir = [dirPaths objectAtIndex:0];
NSFileManager *filemgr = [NSFileManager defaultManager];
NSString *filePath = [docsDir stringByAppendingString:#"/myTestFile.mp4"];
[filemgr moveItemAtPath:file.fileURL.path toPath:filePath error:&error];
if ([filemgr fileExistsAtPath:file.fileURL.path]) {
urlOfAudioFile = [[NSURL alloc] initFileURLWithPath:filePath];
[self uploadToServer:urlOfAudioFile];
}
}
This works absolutely fine if both the WatchApp & the iPhone App are Active.
How can I make it work when the iPhone is in the background/ inactive/ in the locked state?
The documentation on transferFile(_:metadata:) clearly states:
Use this method to send a file that is local to the current device.
Files are transferred to the counterpart asynchronously on a
background thread. The system attempts to send files as quickly as
possible but may throttle delivery speeds to accommodate performance
and power concerns. Use the outstandingFileTransfers method to get a
list of files that are queued for delivery but have not yet been
delivered to the counterpart.
...
This method can only be called while the session is active—that is,
the activationState property is set to activated. Calling this method
for an inactive or deactivated session is a programmer error.
So as per your code:
if ([WCSession isSupported]) {
if ([self.watchSession isReachable]) {
[self.watchSession transferFile:outputURL metadata:nil];
}
}
If the isSupported & isReachable checks fail, then basically WCSession is inactive and your code will not reach the transferFile(_:metadata:) part.
This is the correct behavior and you would have to handle this case manually.
But... when you have a valid session and transferFile(_:metadata:) does get called then whether the iPhone is locked, or the app is in background, or even if the app is not running, it will receive the file via a background thread.
So to answer your question, if the iPhone app is "inactive"; as in isReachable is false then the file transfer will not happen.
Ref:
https://developer.apple.com/documentation/watchconnectivity/wcsession/1615667-transferfile
I am creating a chat messenger app like whatsapp and trying to implement notification functionality similar to whatsapp. In whatsapp when you receive a notification it stores the data somewhere and when you turn off your wifi and go into the app the message is injected in the application. This mean whatsapp is somehow accessing the notification even when application is closed.
My Approach: I am receiving notification in background mode and saving that into a file. So if the user gets disconnected from the internet and goes into the app the messages are still injected on applicationWillAppear. This works perfect but when you forcefully close your app (Double clicking home and swiping the app up) it does not work. I have search almost everything and it says background fetch will not work if you forcefully close your application.
Then how whatsapp is doing it? What can be any other solution?
My Solution:
Turned on Background Modes for Background Fetch and remote notification from XCode. Next added following code inside application:willFinishLaunchingWithOptions:
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
Added this in AppDelegate.m file
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler{
NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES);
if (0 < [paths count]) {
NSString *documentsDirPath = [paths objectAtIndex:0];
NSString *filePath = [documentsDirPath stringByAppendingPathComponent:#"notification.txt"];
NSString *content = [NSString stringWithFormat:#"%#", userInfo];
NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:filePath]) {
// Append text to the end of file
NSFileHandle *fileHandler = [NSFileHandle fileHandleForWritingAtPath:filePath];
[fileHandler seekToEndOfFile];
[fileHandler writeData:data];
[fileHandler closeFile];
} else {
// Create the file and write text to it.
[content writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
}
handler(UIBackgroundFetchResultNewData);
}
Update: I do have following flag in my notification
content-available: 1
Background pushes do not get delivered to a user-terminated app.
Unless it is a voip push, in that case the app will be started by the OS if necessary (but yes you can only make use of voip push if your app provides voip functionality to the user.)
This question already has an answer here:
APN custom notification sound issue
(1 answer)
Closed 9 years ago.
I have a sound file in the app bundle, i want to play that sound file when user will get push notification.
IS it possible in iOS if yes, then please suggest the way to achieve this.
Thanks,
To play this sound you must specify the filename of the sound in the notification payload. For example, lets say you've added a sound file named example.caf into your application, we can play this sound with the notification payload as below:
{
aps =
{
alert = "test example notification message";
sound = "example.caf";
};
}
Then the custom sound will play when your notification arrives.
Use this method in your app delegte class.
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
UIApplicationState state = [application applicationState];
if (state == UIApplicationStateActive)
{
NSLog(#"User Info : %#", [userInfo description]);
NSLog(#"User Info Alert Message : %#", [[userInfo objectForKey:#"aps"] objectForKey:#"alert"]);
NSString *messageString = [NSString stringWithFormat:#"%#", [[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;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops = 0;
[audioPlayer play];
}
}
I have an app that is storing Task objects (custom class) inside of a NSMutableArray. The Task class conforms to the NSCoding and NSCopying protocols, and I have also implemented the encodeWithCoder and initWithCoder methods.
When the user adds a new task to an NSMutableArray list, I save the array using NSKeyedArchiver. The list populates a UITableView.
The data is being saved, and when I exit the app and reenter, the data is still there. When I use another app for a while and come back, the data is still there. However, when I "kill" the app in the multitasking task manage or restart the device, the data disappears. Here are some important code snippets:
#define kFilename #"epapsTasksFile"
.
- (NSString *)dataFilePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:kFilename];
}
- (void)viewDidLoad {
NSString *filePath = [self dataFilePath];
if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
self.list = [[NSKeyedUnarchiver unarchiveObjectWithFile:kFilename] retain];
}
else {
self.list = [NSMutableArray arrayWithCapacity:1];
}
...
}
- (void)applicationWillResignActive:(NSNotification *)notification {
NSMutableArray *updatedList = [[NSMutableArray alloc] initWithArray:self.list];
[NSKeyedArchiver archiveRootObject:updatedList toFile:kFilename];
}
Why is my app not saving the data when the app is "killed" or the device is restarted? Also, it may be interesting to note that when I restart the iPhone simulator, the data stays in place.
You need to save the data
([NSKeyedArchiver archiveRootObject:updatedList toFile:kFilename];
in applicationWillTerminate delegate method as as well to save it on termination.
EDIT:
applicationWillTerminate is not gauranteed in IOS4.0 and above.
Best is to check the return status of archiveRootObject:toFile: and see if the data is stored properly. As you figured it out, it can be case with wrong file path.
I am trying to implement a simple plist example from "Beginning iPhone 3 Development book". I looked into the code but my data was never saved to a plist file. Actually my project site map is as follows: Whenever you launch the app it fires in TestViewController. On the TestViewController, there is a button. When you click on the button it pushes another view controller which is PersistenceViewController and here is the code I wrote in PersistenceViewController. My doubt: is the applicationWillTerminate being called in this method? I don't think so..please help. I am learning how to persist the data now.
In .h file #define kFilename #"data2.plist"
- (NSString *)dataFilePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:kFilename];
return path;
}
- (void)applicationWillTerminate:(NSNotification *)notification {
NSMutableArray *contactFormArray = [[NSMutableArray alloc] init];
NSLog(#"App Terminate:%d",[contactFormArray count]);
[contactFormArray addObject:nameField.text];
[contactFormArray addObject:emailField.text];
[contactFormArray addObject:phoneField.text];
[contactFormArray addObject:companyField.text];
[contactFormArray writeToFile:[self dataFilePath] atomically:YES];
[contactFormArray release];
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSArray *contactFormArray = [[NSArray alloc] initWithContentsOfFile:filePath];
NSLog(#"Did Load:%d",[contactFormArray count]);
nameField.text = [contactFormArray objectAtIndex:0];
emailField.text = [contactFormArray objectAtIndex:1];
phoneField.text = [contactFormArray objectAtIndex:2];
companyField.text = [contactFormArray objectAtIndex:3];
[contactFormArray release];
}
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillTerminate:)name:UIApplicationWillTerminateNotification object:app];
[super viewDidLoad];
}
Thanks for any valuable suggestions...
applicationWillTerminate is called when the user quits the application (usually by pressing the Home button). Any code in this delegate method will be executed (if it doesn't take too much time). In your case, the Plist file will be saved.
If you are using iOS 4, pressing the Home button may send your application into the background (not quitting). If the application is killed using the debugger or crashes, that method will not be called.
Additional Information:
On iOS 4, multitasking is enabled in Xcode projects by default. This prevents the applicationWillTerminate method from being called. If you do not want to support multitasking, place UIApplicationExitsOnSuspend in your MyAppName-Info.plist file and check the checkbox. If you do want to support it, place any code in the applicationDidEnterBackground delegate method that you want to execute before the application enters an inactive state.
applicationWillTerminate: will not be called on multitasking devices (iOS4) if app is suspended and then killed via the multitasking UI. According to Apple: "apps are not aware of any transitions into or out-of the suspended state". So if you're saving anything inside applicationWillTerminate: try to do it in applicationWillResignActive:.
I might be mistaken here, but doesn't applicationWillTerminate have to be coded into the app's delegate.m file, rather than in some other .m file? Regardless, this might not matter due to the suspended state of apps in iOS 4.