Appropriate way to get user's location(also in killed state) for an "hyperlocal" app - ios

Requirement - I am building a hyperlocal app which will provide offers to user based on his location. When in app I can get his current location and show offers accordingly but what I need now is to send push notification to user based on his location. So I want to find out user's location and send offers based on his location.
I have read Apple doc for Significant-Change Location Service but then this answer is saying that it won't work once app is killed.
I have also read about Tracking the User’s Location but that didn't work for me properly. I was not getting more than 5 updates in background.
My current code in viewDidLoad -
if (self.locationManager == nil)
{
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.delegate = self;
self.locationManager.allowsBackgroundLocationUpdates = true;
self.locationManager.pausesLocationUpdatesAutomatically = false;
if ([CLLocationManager authorizationStatus] != AVAuthorizationStatusAuthorized) {
[self.locationManager requestAlwaysAuthorization];
}
}
[self.locationManager startUpdatingLocation];
And my delegate method looks like this -
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
if (UIApplication.sharedApplication.applicationState == UIApplicationStateActive) {
// Get updated data according to location
} else {
// Send location to server
}
}
My apps capabilities And my plist -
Please suggest some appropriate way, I can live with accuracy of around 1km.
Another approaches -
Can I get user's location in "Background Mode- Background Fetch"'s fetchNewDataWithCompletionHandler: ?
Can I get user's location using Silent Push notification's application:didReceiveRemoteNotification:fetchCompletionHandler:? ): Not possible according to this answer

So, your question is basically "How can I get around Apple's specifically designed user interaction for people wanting to make my app shut up?"
The answer to that is "Not if you want to stay within the rules for the AppStore".
Listen, if the user decides to terminate your app (i.e. they swipe it up and out of the task manager), the entire point of this design is for your app to stop do anything. Apple designed this so that people can quickly turn off exactly the kind of behavior you seem to want to implement. I don't quite get why you would want to circumvent that. I agree that this interaction is a bit obscure perhaps (after all, users can also allow/disallow an app's right to receive location updates in the background in the Settings app as well), but that's the interaction iOS defines.
If I understand you correctly, you succeeded in properly setting up the background location modes, so your app can receive the location updates even if it is not in the foreground (i.e. in background or suspended, in the latter situation iOS wakes it up briefly so you can process location updates and e.g. send a local notification to inform the user). That's as good as it gets.
Oh, and don't fear a device reboot. Yes, after the reboot your app is technically not running, but since the user didn't explicitly kill it the last time, iOS treats it like it was in suspended mode, IIRC, so you will still get significant location updates and can react properly.
(In a more general way: people often seem to think the actual app process state reflects the app state as it is defined in the documentation and/or that whether the app is shown in the task manager is linked to that. Both isn't entirely true.)
Edit after your comment asking me specifically about background fetch:
Sorry, that was perhaps not entirely clear. I didn't answer on this sub-question specifically, because after the user quits your app intentionally, you should not, as explained, "cheat" on their intention. Silent push notifications won't work because of this, yes.
I don't know whether the background fetch will be suppressed in a similar way (could be, but I haven't tried it), but I think that won't help you either, even if it were still working (which I doubt, Apple's probably going to be hard on this). I have also never tried to (re)start location updates in this method, so I can't say whether that even works (once the completion handler is called the system will likely suspend your app again at least, and I am not sure that this "resets" the "user killed the app flag" that the system seems to use for deciding which app to wake up and deliver location updates to).
application:performFetchWithCompletionHandler: (as it is fully called) will be called by the OS based on heuristics, it tries to be "clever" on this. Since the method is meant to fetch some data from a backend that doesn't provide push notifications, the times it is called at all can be severely limited, as is explained in the documentation. The actual mechanism that decides when the OS calls is a black box and it is a gamble to try to trick it into getting called when you need it. Could be very likely that this happens hours later. Think of the following scenario:
Your app runs fine in the background location modes (I understood you correctly in that you successfully set that up? see here and here)
The user manually kills your app. For the sake of this argument let's assume that doesn't keep the system from later restarting it to give it a background fetch opportunity
Since other apps are running on the system and the user only has an edge connection at the moment, the OS decides it is a bad time to initiate a background fetch (I just assume here that those factors play a role, as said it's a blackbox, but those appear reasonable to me). Your user walks around some more, bypassing several locations you would be interested in.
Two hours later, the user is by now in a completely different area, your app is started again by the OS and gets a application:performFetchWithCompletionHandler: call. You start location updates again (lets assume that even works and the system doesn't immediately terminate the app again, i.e. even after the fake background fetch it delivers location updates in background). You missed several locations, but nevertheless the app now handles new ones. All your logic is basically messed up, because you didn't plan for so many location updates being missing...
(Optional: After a while your user realizes that your app apparently does something, even although they terminated it (for example they notice battery drain). They will delete your app and leave a one-start review...)
(Optional 2, worst-case: Once apple realizes it is possible to re-init background location updates after a user killed an app this way, they simply close the loop-hole in an iOS update and your app is back where we started...)
For the record: I am not defending any design decisions made by apple, heck I know this is confusing to wrap your head around and just as one can make a stand for "preserve battery and user intent under all circumstances" one can make one for better background tracking. I am merely pointing out that they're in control here and trying to weasel around any specific interaction paradigms they set on the platform is likely not a good idea.
That all being said, I am afraid the "user terminated the app, now I won't get any location updates" is simply something you have to live with. Why would that even be a problem for you, considering you didn't say anything about background location modes not being enough for you? The only (questionable) scenario I could imagine is a kind of tracker application, maybe given on devices handed out to employees of a delivery service or something. If that is it (and putting aside the ethics behind such stuff, in some countries that is even illegal to do, mind you...), I have to say that iOS is simply not the correct platform for this.

Hi just create a UILocalNotification set it with your details and most important for notification to be triggered when you enter a Region add the region property see docs
Hope it helps

Related

React Native iOS app restart on reboot

I want to create an iOS app using React Native. One of the primary features of the app is that it runs constantly in the background. It also requires using GPS btw (in case that is important).
I have had a number of devs tell me its not possible to do this for iOS, however I have read that it is do-able.
Is this possible to do in the iOS environment? Mainly, if the app is running when the phone is powered off, can you make it open when the device is restarted without the user opening it?
I should say that I am a RN novice and any help is much appreciated.
If your app gets permission to get location while it's not in the foreground, then you will get periodic updates and some time to process it.
For example, if you are giving driving directions.
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html#//apple_ref/doc/uid/TP40009497-CH2-SW10
If there is no good user benefit for you to get the location in the background constantly, you might be rejected. Apple suggests region monitoring instead
iOS supports the delivery of location events to apps that are suspended or no longer running. The delivery of location events in the background supports apps whose functionality would be impaired without them, so configure your app to receive background events only when doing so provides a tangible benefit to the user. For example, a turn-by-turn navigation app needs to track the user’s position at all times and notify the user when it’s time to make the next turn. If your app can make do with alternate means, such as region monitoring, it should do so.
I have no idea how RN wraps this behavior, but no matter what it does (or what a plugin might do), the core iOS behavior is how it is described in that URL.
I verified yesterday (at least on iOS 11.2 simulator) that automatically restarting the app (and the location tracking) after phone reboot works.
The key point is that startMonitoringSignificantLocationChanges needs to be on to wake up the app after reboot. For me the difficult part was figuring out when to turn it on, because I couldn't find a reliable way to detect when the phone is being rebooted or run any code at that point. However, based on initial testing it looks like startMonitoringSignificantLocationChanges is independent and doesn't negatively impact the usual location updates, so now I just leave it on all the time and toggle startUpdatingLocation / stopUpdatingLocation on top of it based on Core Motion-detected activity.
Otherwise requirements are the same as for any location tracking on the background, i.e. handle permissions and don't process too much. Apple documentation explains how to detect that the app was relaunched by a location event.
Here's a react native module which basically helps you achieve what you're describing: https://github.com/transistorsoft/react-native-background-geolocation

Getting continous user location and based on that sending information to server

I've got a concept of adding feature to my app which will register user location in interval of few seconds. Then sending this coordinates through cellular or Wi-Fi all when staying active in background. I need to send this in almost real time, so the app can't be killed in backgorund.
I know this concept is very power consuming but it is only a conception.
The conception of getting constant location in backgorund is in this theard https://forums.developer.apple.com/thread/69152
so I think it is possible.
But refreshing only the process in my app of sending coordinates to server it's a little bit difficult. I could not get straight answer that it is possible to set the time interval in which the app will refresh in backgrund.
Is there a method for telling the app how often it should refresh in background?
Apple won't let your app idea onto the app store. They're quite strict about which types of apps are allowed to run in the background, and are allowed to use continuous GPS. (Both of those things really drain the user's battery.) Sending a continuous stream of location updates to a server will also keep the cellular/WiFi transmitter powered up, making things even worse.
For your own experimentation, though, you can probably set up your app to be a navigation app. Those are allowed to keep the GPS "lit" constantly and to run in the background. You could then set up the location manager with the highest accuracy setting and start updating your location.
I don't think you have the ability to control how often you get location updates though. You could create a timer that fires on a regular interval and fetches the current location from the location manager. However I don't think there's much value in that, since you WILL get called when the user's location changes, and fetching the current location more often will just give you the same answers repeatedly.

iOS didEnterRegion sometimes not get called

I am working on xcode 8.3.3 on a project written in Swift2.3.
I installed SwiftLocation (2.3 Branch) library via Cocoapods in order to monitor region events (didEnter/didExit Region).
I had 3/4 region to monitor on average at the same time
I add them as it follows:
do {
let monitor = try Beacons.monitor(geographicRegion: epicenter, radius: 100, onStateDidChange: { (state) in
state == .Entered ? self.delegate?.checkedIn(id, status: .Awake, onFinish: nil) : self.delegate?.checkedOut(id, status: .Awake, onFinish: nil)
}) { (error) in
let error = NSError(domain: “Monitor”, code: error._code, userInfo: [NSLocalizedDescriptionKey : error.description])
self.delegate?.failedBecause(error)
}
monitor.onAuthorizationDidChange = { status in
self.delegate?.movedTo(status)
}
} catch let error {
let error = NSError(domain: “Monitor”, code: error._code, userInfo: [NSLocalizedDescriptionKey : error as! AnyObject])
self.delegate?.failedBecause(error)
}
I have activated Background Modes and asked for Authorization and I am sure it is working in background because the app is woke up and I am able to get events even if the app has been killed.
Moreover both the gps and the internet coonection on the device are turned on. The feature seems to work well, but often the callback “onStateDidChange” of the method “Beacons.monitor(:)” doesn’t get called. It’s really weird that I have a great number of exit event, but not too many entrance.
We already tested if this situation occurs only because the device was inside the region when monitoring was added but this seems to be not the reason. How is possible to have sometimes only the exit event but not the entrance? To intercept the exit event, the system knows that the device was inside the region, but the entrance event hasn’t been triggered.
Any help on fix this or debugging it is really appreciated.
Hi #Gero. Thanks for your answer.
Your scenario is likely to happend.
I simulated it on the XCode Simulator either killing the app directly or waiting more the 5 minutes with the app in background (since Apple documentation suggests that background tasks are killed after 3 minutes). In both situation UIApplicationLaunchOptionsLocationKey is not empty and it tells me that the app has been woken up.
In this case, I execute a piece of code that just instantiate the LocationManager and set the controller as delegate without restarting regionMonitoring.
I have evidences on the server side that didEnter/didExit is called via this flow. I know this because the app makes an HTTP call to store the event and sends “asleep” as a parametern to distinguish it from the “standard” flow in which it sends “awake”.
For this reason I am afraid that this is not the reason, because it would be still not clear why it is sometimes working and sometimes not.
We’ll make a try with requestState(for:) and let you know :wink:
Okay, this is only an educated guess, but since it's too long for a comment I'll try to warp it into an answer. Your comments and (excellent) edit indicate to me you know what you're doing and employ the correct methods (sidenote: A lot of questions on this topic result from improper understanding of the feature, so at first I thought it's maybe that).
What I could imagine happens is this:
The app is running while you're outside of the region, i.e. the next thing would be a .Entered kind of state in the callback. However, the app gets terminated by the OS before that happens for some reason (memory?).
Next you enter the region, so the app gets relaunched. You properly figure this out checking for the UIApplicationLaunchOptionsLocationKey in the launchOptions dictionary and start your region monitoring again. However, I think the system might then not explicitly deliver the state change via callback again. The documentation is not that clear on this for region monitoring. So now you're inside the region, but never received an according state change callback.
The solution would be to explicitly query the region if and only if your app is (re)launched with UIApplicationLaunchOptionsLocationKey using requestState(for:) of CLLocationManager and your region (this might be tedious if you have multiple regions, but is probably required).
Edit: For what it's worth I stumbled over a related question on the dev forums and even clarified the issue further there. Gualtier Malde from the Apple staff answered, so that should be a valid source. In case the link vanishes (as sometimes seems to happen), here a summary and quotes of what he explained.
The basic question was about rebooting (which terminates an app) and then the app not getting region updates. His first answer was
After a reboot, your app will not be alerted for iBeacon events until the user unlocks the device first. There is no way around this.
If you are having issues with iBeacon detection even after the user unlocks the device, barring any coding problems with your app, the first place to look at is the iBeacon advertising, and to make sure it is advertising 100% to the specs, and is also calibrated properly for its installed location.
I then further asked when exactly the resume is about to happen. I wanted to know whether because of this it's theoretically possible to miss region updates and when exactly the region updates should come in again. Answer:
Yes, your app should get relaunched in the background - considering the beacons are advertising correctly, Bluetooth is turned on, etc. - without user interaction.
All this will happen only after the first unlock. Nothing will happen with your app after a reboot until after the first unlock.
Any events that happen before the first unlock can be considered lost to your app.
From this I take that even a relaunched app gets region updates. I have no idea why you miss any, unless you actively reboot the device.
One thing to note regarding your testing procedure though:
Putting the app in background and waiting > 5 minutes will not automatically terminate the app. It will usually simply put it into the suspended state. The documentation you refer to does indeed explain that background tasks are terminated after a certain time (note that they do not guarantee a definite time here, Apple stays intentionally vague here to remain in control for future changes and stuff), but that does not mean your app process is necessarily killed! The tasks are just ended and your app is suspended. There is no real way for us to have the OS terminate an app. AFAIK it would do that if it needs the resources, but that's hard to enforce somehow. Besides, I am also pretty certain that a debug build on a development device (especially connected to Xcode/the debugger) doesn't necessarily behave like an iPhone in the wild. The simulator behaves even more different.
All that said, I am even more surprised that you get an application(_:didFinishLaunchingWithOptions:) call, because the way you described how you terminated the app shouldn't even result in it being terminated in a regular, okay way. Maybe you do have a bug, it crashes (or even gets "punished-quit" by the OS?) or the like? I don't know if an app that for example tries to "maliciously" buy background processing (e.g. by answering the application(_:performFetchWithCompletionHandler:)'s completion handler dishonestly or something?) time might be exempted by the system from region updates? That might be the case, but we're way beyond the scope of this question. It all boils down to an application state issue, i.e. life-cycle problem. You have to be very careful to properly identify what state you're trying to debug and expect to be in.

How to do BG NW download after geofence didEnterRegion/didExitRegion notice

I am developing a client/server app for a client where the clients are iOS devices.
One client module is for traveling salespeople.
My client wants the salesperson's app to automatically download it's local database when the salesperson leaves the office, and when the salesperson returns to the office.
I am setting up a geofence using the location manager's startMonitoringForRegion method, and will look for the UIApplicationLaunchOptionsLocationKey on launch, as well as looking for calls to the location manager locationManager:didEnterRegion/locationManager:didExitRegion methods from the background.
My question is, how do I ask the system to allow me time to make a network connection and download new data in response to either an app launch with a UIApplicationLaunchOptionsLocationKey, or background calls to locationManager:didEnterRegion/locationManager:didExitRegion?
None of the background mode keys (UIBackgroundModes) look like the right fit.
I don't need the location key, since I don't need live location information, just geofence enter/exit messages. The fetch key isn't what I need either, since that causes the system to wake my app up in the background at times it chooses.
Do I have to have one of the background mode keys set in order to make a beginBackgroundTaskWithExpirationHandler call?
I guess what I should do is submit my network request in my beginBackgroundTaskWithExpirationHandler in my locationManager:didEnterRegion/locationManager:didExitRegion methods. Will the system allow me to do that without having a UIBackgroundModes key set?
And can I make a call to beginBackgroundTaskWithExpirationHandler if I'm already running from the background? (In the case where the app is running but not frontmost and a locationManager:didEnterRegion or locationManager:didExitRegion call comes in.) My goal is to ask for enough background time to process my network download and save the data to my local database.
Any guidance from somebody who's done something like this would be a big help. The docs are pretty vague, and figuring out how to use the background APIs by trial and error is likely to be pretty time-consuming.
P.S. I'm developing this app in Swift, although I'm a longtime Objective-C developer. I'm comfortable translating Objective-C to Swift or mixing Objective-C and Swift as needed, so examples in either language would be useful.
It is OK to call beginBackgroundTaskWithExpirationHandler from background. As of iOS8 this call will extend your run time to 180 seconds from 10s.
Keep in mind that you need to request Always authorization for location services, that's what is required to run region monitoring.
I suggest you do use the background location key. I forgot to set it in one of my apps, and it still was receiving the region enter/exit events. However,
-- the behavior might change with minor iOS upgrade, and also
-- the app might be rejected by Apple

Keep an app active in the background?

I have a volume control app in the iOS store, but one problem that my users have frequently is that the device kicks it for memory control. Is there any way to either force it to stay active (by permission) or to at least alert the user when its no longer active or in danger?
All the answers are under Background Execution in the iOS Developer Library.
Of course, here's the philosophy:
Always try to avoid doing any background work unless doing so improves
the overall user experience.
See Table 3-1 for the types of background execution. There's a category for audio but it requires audio to be played from the app. It does not sound like your app fits in here. So you'll want to look at notifying the user.
You could send a notification to the user when the app falls to background with applicationDidEnterBackground(), or just before it quits with applicationWillTerminate(), supposedly time-permitting.
Running in the background is permitted. I have an app that does it and while the rules have changed and adapting to it has been gut wrenching at times, it is pretty straightforward. In your case, I would think that you could setup to be notified and then just disappear, no? waking from a notification is part of the state changing protocol and you should be able to do that.
As a point of comparison, in Android, background operations like yours would be implemented as services and would have no fear of being terminated. The strangest thing about the way ios chooses to allow background activity is that you are applying for status one time. In Android, I was able to indicate that a single activity should be kept alive when the app is backgrounded, all others could be terminated. There is no way to do that in ios.
I take it my disappear and wait for notifications is probably not going to work for you because there is no way to be notified of volume changes. You must be polling? In which case, you probably are going to have to ask to be kept alive. Would be nice if you could just piggyback on other services, e.g. keep me alive while music is playing.
To answer your other question, yes you will get notified if/when you really are going to get termed, so you could send a notification at that point.

Resources