I'm trying to make an API call after getting "didEnterRegion" call. When the app is on front, it works great. But if the app is deactivated or even in background, the call is never made. I tried to debug it but once "reportLocationEventToServerWithType" is called the debugger "dies" and the call is never made. I don't think it matters, but i'm using Estimote sdk for the beacon monitoring.
Here are the code snippets:
- (void)beaconManager:(id)manager didEnterRegion:(CLBeaconRegion *)region{
UIApplication *app = [UIApplication sharedApplication];
NSNumber * mallID = [self.dictBeaconsPerMall objectForKey:region.identifier];
Mall * mall = [Mall getMallWithID:mallID];
[ApplicationManager sharedInstance].locationManager.beaconRecognizedMall=mall;
[[NSNotificationCenter defaultCenter]postNotificationName:nLocationMallUpdated object:nil];
UILocalNotification * not = [[UILocalNotification alloc]init];
not.alertBody = [NSString stringWithFormat:#"DID Enter beacon region at: %#",mall.strTitle];
not.alertAction = #"alertAction";
not.soundName = UILocalNotificationDefaultSoundName;
[app presentLocalNotificationNow:not];
[[ApplicationManager sharedInstance].crashAndReportsManager reportLocationEventToServerWithType:#"beacon" andSubParam:region.identifier extraInfo:#{#"paramKey":#"key"}];
}
-(void)reportLocationEventToServerWithType: (NSString*)type andSubParam:(NSString*) strID extraInfo: (NSDictionary*)extraInfo{
NSString* udid = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
NSMutableDictionary * params = [[NSMutableDictionary alloc]init];
[params setObject:type forKey:#"type"];
[params setObject:strID forKey:#"subParam"];
[params setObject:udid forKey:#"udid"];
if(extraInfo)
[params addEntriesFromDictionary:extraInfo];
UserLogRequest * req = [[UserLogRequest alloc]initWithCallerObject:self andParams:params];
req.showHud=NO;
req.showMessage=NO;
[[ApplicationManager sharedInstance].requestManager sendRequestForRequest:req];
}
The request initialization and the request manager "sendRequestForRequest" are working fine in million other situations so their implementation is irrelevant.
Thanks!
Background execution is little bit different in ios. you can't execute in background without some settings from capabilities under project target. you should enable background execution from it for specific requirement like location, audio etc. and then you should implement code accordingly.
You have limited options to execute in background like audioplaying, location updating, finite length task, voice over ip etc.
refer this apple documentation for more detail on background execution and you can google background execution you will got some good results.
So, reason of not working your task in background is this that you have not properly configured it.
hope this will help :)
Related
I have a button in WatchKit that sends a notification to the main iPhone app like this.
-(IBAction) startSound
{
//turn sound on
NSString *requestString = [NSString stringWithFormat:#"startSound"]; // This string is arbitrary, just must match here and at the iPhone side of the implementation.
NSDictionary *applicationData = [[NSDictionary alloc] initWithObjects:#[requestString] forKeys:#[#"startSound"]];
[WKInterfaceController openParentApplication:applicationData reply:^(NSDictionary *replyInfo, NSError *error) {
//NSLog(#"\nReply info: %#\nError: %#",replyInfo, error);
}];
}
In my iPhone app delegate I have added the following code.
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply
{
NSLog(#"handleWatchKitExtensionRequest ...");
NSMutableDictionary *mutDic = [[NSMutableDictionary alloc] init];
//This block just asks the code put after it to be run in background for 10 mins max
__block UIBackgroundTaskIdentifier bgTask;
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
bgTask = UIBackgroundTaskInvalid;
}];
NSString *request = [userInfo objectForKey:#"startSound"];
if ([request isEqualToString:#"startSound"])
{
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource: #"warning" ofType: #"mp3"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
myAudioPlayer1 = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
myAudioPlayer1.numberOfLoops = -1; //inifinite
[myAudioPlayer1 play];
}
reply(nil); //must reply with something no matter what
//once code is all done and the reply has been sent only then end the bg-handler
[application endBackgroundTask:bgTask];
}
Yet, when my app went for apple review, it got rejected for reasons that my app had to be running in the foreground for the sound feature to work. What did I miss?
10.6 - Apple and our customers place a high value on simple, refined, creative, well thought through interfaces. They take more work but are
worth it. Apple sets a high bar. If your user interface is complex or
less than very good, it may be rejected
10.6 Details
We still found that your Apple Watch app requires the containing app
to be running in the foreground on iPhone in order to play siren
sounds, which provides a poor user experience.
Next Steps
Please see the UIApplicationDelegate Protocol Reference to implement
this method and use it to respond to requests from the Apple Watch
app.
Because this method is likely to be called while your app is in the
background, call the beginBackgroundTaskWithName:expirationHandler:
method at the start of your implementation and the endBackgroundTask:
method after you have processed the reply and executed the reply
block. Starting a background task ensures that your app is not
suspended before it has a chance to send its reply.
How long is the audio clip? In the code you've shared it looks like reply() would be called almost immediately, which wouldn't give the clip a chance to play. You should delay calling reply() until your audio clip has completed.
I do something very similar in my WatchKit app. When the user taps on a button I play audio from my iPhone app and my iPhone app does not need to foreground for it to work. There were two things I had to do to get it to work. The first was setting up a background task with beginBackgroundTaskWithName, which I can see you are doing. The second was to enable background audio capabilities in my background modes.
I don't remember doing anything other than those two to get it to work. I did already have background audio working in my app before I added support so it could be controlled with MPNowPlayingInfoCenter.
I've been struggling for the past few days on the local notifications on my app.
Basically the goal is to pop a notification when the user approches an address.
here is the code:
NSMutableArray *notifications = [#[] mutableCopy];
for (CCAddress *address in results) {
CCCategory *category = [address.categories.allObjects firstObject];
NSDictionary *userInfo = #{#"addressId" : address.identifier};
UILocalNotification *localNotification = [UILocalNotification new];
if (category == nil)
localNotification.alertBody = [NSString stringWithFormat:#"Vous ĂȘtes proche de %#", address.name];
else
localNotification.alertBody = [NSString stringWithFormat:#"Vous ĂȘtes proche de %#, %#", address.name, category.name];
localNotification.alertAction = #"Linotte";
localNotification.soundName = UILocalNotificationDefaultSoundName;
localNotification.userInfo = userInfo;
[notifications addObject:localNotification];
address.lastnotif = [NSDate date];
}
[managedObjectContext saveToPersistentStore:NULL];
[UIApplication sharedApplication].scheduledLocalNotifications = notifications;
The result is actually totally random, but there is something I know for sure: the geofencing works well, as you can see I set the date of the notification in lastNotif, so I know when they are fired.
Sometimes I see the notification pop, but doesn't stay in the notification center, but most times nothing happens, even if I see by the date that It actually fired, and sometimes everything goes fine.
I tried many things, like using presentLocalNotificationNow, setting a fireDate with a 1 second delay between each, and other things I don't even remember...
So, obviously there is something I missed in the documentation, but what ?
thanks.
PS: the app is in background or off when it happens, I'm aware of didReceiveLocalNotification.
PS2: I actually don't know if those that I don't see at all actually fired, because they don't show up in the notification center, so maybe they fired but I have absolutely no way to see them if I don't have my phone's screen in sight when they do.
EDIT: So, I've been doing some tests around my house, phone closed, screen locked. The real syndrom is that when a notification pops, it only turns the screen on, and the phone vibrates (I was sound off), then nothing...
I don't see that you're setting a fireDate. I can't recall what that defaults to.
Ok, so as intended, it was kind of stupid.
Before the code I posted I had:
if ([results count] == 0) {
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
return;
}
But, when you set applicationIconBadgeNumber to 0, it removes all notifications from notification center !
The documentation says nothing about this (https://developer.apple.com/library/ios/documentation/uikit/reference/UIApplication_Class/Reference/Reference.html).
You need to add registerUserNotificationSettings in didFinishLaunchingWithOptions it give us notification Alert
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([UIApplication instancesRespondToSelector:#selector(registerUserNotificationSettings:)]){
[application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil]];
}
UILocalNotification *localNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotification) {
application.applicationIconBadgeNumber = 0;
}
return YES;
}
I'm working on an app that alerts the user when he is close to some landmarks using the region monitoring. Everything works fine but when the app is in the background I don't get the alerts. When I open the app I get all the alerts popping up. What I wanted was to get them when the app is in the background. I'm wondering if it's possible or does the app needs to be running to get alerts? Any help would be greatly appreciated.
Update:
The problem seems to be that I used Alerts instead of local notifications. Here's the code I used:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(#"Entered Region - %#", region.identifier);
[self showRegionAlert:#"You are near: " forRegion:region.identifier];
}
How can I change this to local notifications?
Check the "testing your apps region monitoring" section in
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/LocationAwarenessPG/RegionMonitoring/RegionMonitoring.html
If you switch back and forth between foreground and background the threshold conditions might not be met and trigger before you bring the app to the foreground again.
Also when the backgrounded app gets a notification there is only a small window for processing the message. Trying to do network requests might time out...
Check your plist settings - declare location as UIBackgroundModes only required if you need high precision positioning. The significant location changes works even without location defined.
Check that locationManager:didUpdateLocations: and locationManager:didFailWithError: are being called and no errors are posted.
Check that you haven't set ApplicationRunsInBackground to NO in your plist.
Try implementing the AppDelegates applicationDidEnterBackground:, application:didFinishLaunchingWithOptions: and friends to spot where in the app lifecycle you are at a given time.
For geofencing solution, you can refer the answer given by #Niels Castle.
For local notification, you can refer below code:
UILocalNotification* localNotification = [[UILocalNotification alloc] init];
localNotification.alertBody = #"I'M IN THE REGION";
localNotification.userInfo = [[NSDictionary alloc]initWithObjectsAndKeys:#"nearBy",#"type", nil];
localNotification.timeZone = [NSTimeZone defaultTimeZone];
localNotification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(#"Yes! Welcome to %#",region.identifier);
UILocalNotification* notify = [UILocalNotification new];
notify.alertBody = [NSString stringWithFormat:#"Welcome to %#",region.identifier];
notify.soundName = UILocalNotificationDefaultSoundName;
if (notify.applicationIconBadgeNumber == 0) {
notify.applicationIconBadgeNumber = 1;
}
[[UIApplication sharedApplication] presentLocalNotificationNow:notify];
}
i'm not receiving newsstand notifications when the app is not running, here is what I have done.
The app has the proper plist keys 'UINewsstandApp = YES' and 'UIBackgroundModes = newsstand-content'.
In the app delegate I register for all notification types, and i receive my token from APNS, from the server side ( i am using a gem called Grocer ) i set up the dev certificate and send a regular push and it works.
if i send a newsstand push I receive it if the app is running on 'didReceiveRemoteNotification', but when the app is not running i get nothing in the notification center, which is mainly because 'Grocer' has the following payload
{"aps": {"content-available":1}} and can't add any other keys ( alert, badge, etc )
so I thought that I should not get anything in the notification center, I look for the 'UIApplicationLaunchOptionsRemoteNotificationKey' in the launch options, and then write a file to make sure that the app ran in the background, the file is never written as such
NSDictionary *remoteNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if(remoteNotif)
{
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [cachePath stringByAppendingPathExtension:#"pushreceived.txt"];
[#"testing" writeToFile:filePath atomically:YES encoding:NSASCIIStringEncoding error:nil];
}
I have NKDontThrottleNewsstandContentNotifications set to true in my user defaults, and then I synchronize to make sure.
when the app is running, no matter how many times i send the push, I always get a callback on " didReceiveRemoteNotification ", with the proper "content-available"
if the app is closed or in the background, nothing happens.
Update:
I managed to change the gem that sends the notification payload, here is the dictionary it sends
{"aps"=>
{
"alert"=>"Hello iPhone!!",
"content-available"=>1,
"badge"=>1,
"sound"=>"default"
}
}
and here the userinfo dictionary I receive on my app ( while running )
{
aps = {
alert = "Hello iPhone!!";
badge = 1;
"content-available" = 1;
sound = default;
};
}
please note the quotation marks around content-available, does this mean that APNS parsed it as a custom key ?
To receive notifications when your application is running in the background, you need to add a applicationDidEnterBackgroud method to your main class. From the Apple documentation:
Applications might also find local notifications useful when they run in the background and some message, data, or other item arrives that might be of interest to the user. In this case, they should present the notification immediately using the UIApplication method presentLocalNotificationNow: (iOS gives an application a limited time to run in the background). Listing 2-2 illustrates how you might do this.
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"Application entered background state.");
// bgTask is instance variable
NSAssert(self->bgTask == UIInvalidBackgroundTask, nil);
bgTask = [application beginBackgroundTaskWithExpirationHandler: ^{
dispatch_async(dispatch_get_main_queue(), ^{
[application endBackgroundTask:self->bgTask];
self->bgTask = UIInvalidBackgroundTask;
});
}];
dispatch_async(dispatch_get_main_queue(), ^{
while ([application backgroundTimeRemaining] > 1.0) {
// Replace this code with your notification handling process
NSString *friend = [self checkForIncomingChat];
if (friend) {
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
if (localNotif) {
localNotif.alertBody = [NSString stringWithFormat:
NSLocalizedString(#"%# has a message for you.", nil), friend];
localNotif.alertAction = NSLocalizedString(#"Read Message", nil);
localNotif.soundName = #"alarmsound.caf";
localNotif.applicationIconBadgeNumber = 1;
[application presentLocalNotificationNow:localNotif];
[localNotif release];
friend = nil;
break;
}
}
}
[application endBackgroundTask:self->bgTask];
self->bgTask = UIInvalidBackgroundTask;
});
}
Also note:
Because the only notification type supported for non-running applications is icon-badging, simply pass NSRemoteNotificationTypeBadge as the parameter of registerForRemoteNotificationTypes:.
Try these as a sanity check:
In Settings -> Newsstand, you have automatic content download as on for your app.
Make sure that you used [[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeNewsstandContentAvailability] somehwere in your app.
Make sure that you have required background mode as 'newsstand-content' in your plist.
Run the app in the device, not in the simulator.
If you have done those correctly you should get a notification in didReceiveRemoteNotification even when your app is not running.
My app got rejected by Apple three times, all with the same rejection letter, which is:
We found that your app uses a background mode but does not include
functionality that requires that mode to run persistently. This
behavior is not in compliance with the App Store Review Guidelines.
We noticed your app declares support for location in the
UIBackgroundModes key in your Info.plist but does not include features
that require persistent location.
It would be appropriate to add features that require location updates
while the app is in the background or remove the "location" setting
from the UIBackgroundModes key.
If you choose to add features that use the Location Background Mode,
please include the following battery use disclaimer in your
Application Description:
"Continued use of GPS running in the background can dramatically
decrease battery life."
For information on background modes, please refer to the section
"Executing Code in the Background" in the iOS Reference Library.
Now, as far as I know I am running on the background and "doing something"...
In my AppDelegate I have the following code in didFinishLaunchingWithOptions:
if ([[launchOptions allKeys] containsObject:UIApplicationLaunchOptionsLocationKey] &&
([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]))
{
id locationInBackground = [launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey];
if ([locationInBackground isKindOfClass:[CLLocation class]])
{
[self updateMyLocationToServer:locationInBackground];
}
else
{
//Keep updating location if significant changes
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
self.bgLocationManager = locationManager;
self.bgLocationManager.delegate = self;
self.bgLocationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
[bgLocationManager startMonitoringSignificantLocationChanges];
}
}
The AppDelegate also starts a location manager and makes himself the delegate.
Then, I have the following code for handling the location updates on the background:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
[self updateMyLocationToServer:newLocation];
}
-(void)updateMyLocationToServer:(CLLocation*)myNewLocation
{
// NSLog(#"Updating Location from the background");
NSString *fbID = [NSString stringWithString:[facebookDetails objectForKey:#"fbID"]];
NSString *firstName = [NSString stringWithString:[facebookDetails objectForKey:#"firstName"]];
NSString *lastName = [NSString stringWithString:[facebookDetails objectForKey:#"lastName"]];
NSString *urlString = [NSString stringWithFormat:#"MY_SERVER_API", fbID, myNewLocation.coordinate.latitude, myNewLocation.coordinate.longitude, firstName, lastName];
NSURL *url = [NSURL URLWithString:urlString];
__block ASIHTTPRequest *newRequest = [ASIHTTPRequest requestWithURL:url];
[newRequest setCompletionBlock:^{
}];
[newRequest setFailedBlock:^{
}];
// [newRequest setDelegate:self];
[newRequest startAsynchronous];
}
I also put a disclaimer in my app description page:
Intensive use of GPS running in the background can dramatically decrease battery life. For this reason, MY_APP_NAME runs on the background just listening for significant location changes.
Is there anything I'm missing here?
This question is old and already answered but you dont need the UIBackgroundModes key if you collect locations using the startMonitoringSignificantLocationChanges API
In locationManager:didUpdateToLocation:fromLocation: or in updateMyLocationToServer: You should check if application is in background state by eg.
[UIApplication sharedApplication].applicationState == UIApplicationStateBackground
And then if Your app is in background mode You should use eg.
backgroundTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:
^{
[[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
}];
/*Write Your internet request code here*/
if (bgTask != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
bgTask = UIBackgroundTaskInvalid;
}
This way application should perform this task completely.
startMonitoringSignificantLocationChanges don't require a background mode registration. Only continuous location changes do.