iOS CoreLocation, Determine user leaving a location - ios

I've been working in a iOS location tracking application, and we've found a way to determine when users are leaving a place, but we're doing it by constantly listening to location updates, which ends draining our battery.
What is the most efficient way to do a thing like this? i would like to get a battery consumption similar to the reminders application.
Any idea is welcome!
Thanks!

You should set up your app to use geofences.
The method you want to look at is the CLLocationManager method startMonitoringForRegion. You need to set up your app to ask the user for permission to monitor location updates.
The system will launch your app to deliver a region update if it's not running.

It depends on your definition of "leaving a place". But you can use
func startMonitoringSignificantLocationChanges()
On CLLocationManager in order to be notified (roughly) when the user moves 500 meters or more, according to Apple documention.
This shifts the responsibility of watching battery life over to Apple's code, which as you might imagine is well optimized for the task.

Follow #rschmidt's function for starting location update on device.
Track user's current locality by keeping a variable named 'currentLocality' in the class which is delegating CLLocationManager.
Implement the CLLocationManagerDelegate in the following way
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var currentLocation = locations.last as? CLLocation
// Getting current locality with GeoCoder
CLGeocoder().reverseGeocodeLocation(currentLocation, completionHandler: {(placemarks, error) in
if (error != nil) {
println("reverse geodcode fail: \(error.localizedDescription)")
}
else {
let placeMark = placemarks.last as? CLPlacemark
if let latestLocality = placeMark!.locality {
if latestLocality != currentLocality {
// Locality has been changed, do necessary actions
// Updating current locality
currentLocality = latestLocality
}
}
}
})
}

Related

how to prevent mock GPS location (spoofing) from fake GPS app in Swift? [duplicate]

This question already has answers here:
iOS detect mock locations
(5 answers)
Closed 4 years ago.
I am making an app that using CLLocationManager, this app will be used to record the attendee of the employee. to validate the attendee of the employees, we will get GPS Coordinate.
as far as I know, in there is an app that usually used to get fake GPS. I want to prevent this mock GPS to be active when the user using my app.
If I am using Android, I can download a fake GPS app. and when let say I use tinder I can fake my location. let say actually I am in Bangkok, but because I use fake GPS app, I can set my tinder location to be in London, not in Bangkok anymore.
So Basically I want to prevent fake location that comes from that fake GPS when the user using my App. To be honest I don't really know whether iOS allow fake location or not
can I get that function in Swift?
here is the class LocationManager I use to get the coordinate
import UIKit
import CoreLocation
class LocationManager: NSObject {
let manager = CLLocationManager()
var didGetLocation: ((Coordinate?) -> Void)?
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestLocation()
}
func getPermission() {
// to ask permission to the user by showing an alert (the alert message is available on info.plist)
if CLLocationManager.authorizationStatus() == .notDetermined {
manager.requestWhenInUseAuthorization()
}
}
}
extension LocationManager : CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
manager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {
didGetLocation?(nil)
return
}
let coordinate = Coordinate(location: location)
if let didGetLocation = didGetLocation {
didGetLocation(coordinate)
}
}
}
private extension Coordinate {
init(location: CLLocation) {
latitude = location.coordinate.latitude
longitude = location.coordinate.longitude
}
}
You can check if the app is jailbroken or not. If it already jailbroken, you can prevent the user to use the app with showing permanent dialog or something else.
If you wanna know how to detect the device is jailbroken or not, you can find it by yourself. There is so many literature that will tell you how.
Cheers :)
Only thing I can tell you and I've been in a similar situation is you need to have a backup method to get the user location. Use an IP location API/service (which is not 100% reliable) and create a logic in your app to compare the data.
PS: THIS IS NOT A SOLUTION TO YOUR PROBLEM it's just an idea you could try to work with. But this would only work best if spoofing the location is happening using different cities/states since IP location is not a high accuracy one. If GPS says you are in San Diego but your IP say you are in San Francisco, then you could block the UI/request until user confirms something.
PS2: in iOS the only way I know a user can spoof it's location is running an app through XCode, using the location feature and then opening your app. (used to do that a lot with pokemon go #notproud :))

Receiving Location even when app is not running in Swift

Still very new to Swift. I have come from an Android background where there is BroadcastReceiver that can deliver location info to a service even though the app isn't running.
So I was looking for something similar in iOS/Swift and it appears that before this wasn't possible but it may be now. I am developing for iOS 10 but would be great if it was backwards compatible.
I found
startMonitoringSignificantLocationChanges
which I can execute to start delivering location updates, although this raises a few questions. Once I call this and my app is NOT running, are the updates still being sent ? And how would the app wake up to respond ?
Also restarting the phone and when it return, does this mean I still need call startMonitoringSignificantLocationChanges again meaning that I would have to wait for the user to execute my app. Or does it remember the setting after reboot ?
Still a little confused how to get around this, here's a brief explanation of what I am trying to do.
I would like to update the location of the phone even though the app is not running, this would be sent to a rest service every so often.
This way on the backend services I could determine if somebody is within X meters of somebody also and send them a push notification.
It may or may not be a good solution but if I were you I would have used both startMonitoringSignificantLocationChanges and regionMonitoring.
Here is the sample I made which worked well with iOS 13.
Lets take regionMonitoring first. We have certainly no problems when the app is in foreground state and we can use the CLLocationManager's didUpdate delegate to get the location and send it to the server.
Keep latest current location in AppDelegate's property, lets say:
var lastLocation:CLLocation?
//And a location manager
var locationManager = CLLocationManager()
We have two UIApplicationDelegates
func applicationDidEnterBackground(_ application: UIApplication) {
//Create a region
}
func applicationWillTerminate(_ application: UIApplication) {
//Create a region
}
So whenever the user kills the app or makes the app go to background, we can certainly create a region around the latest current location fetched. Here is an example to create a region.
func createRegion(location:CLLocation?) {
if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
let coordinate = CLLocationCoordinate2DMake((location?.coordinate.latitude)!, (location?.coordinate.longitude)!)
let regionRadius = 50.0
let region = CLCircularRegion(center: CLLocationCoordinate2D(
latitude: coordinate.latitude,
longitude: coordinate.longitude),
radius: regionRadius,
identifier: "aabb")
region.notifyOnExit = true
region.notifyOnEntry = true
//Send your fetched location to server
//Stop your location manager for updating location and start regionMonitoring
self.locationManager?.stopUpdatingLocation()
self.locationManager?.startMonitoring(for: region)
}
else {
print("System can't track regions")
}
}
Make use of RegionDelegates
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("Entered Region")
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
print("Exited Region")
locationManager?.stopMonitoring(for: region)
//Start location manager and fetch current location
locationManager?.startUpdatingLocation()
}
Grab the location from didUpdate method
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if UIApplication.shared.applicationState == .active {
} else {
//App is in BG/ Killed or suspended state
//send location to server
// create a New Region with current fetched location
let location = locations.last
lastLocation = location
//Make region and again the same cycle continues.
self.createRegion(location: lastLocation)
}
}
Here I have made a 50m region radius circle. I have tested this and it is called generally after crossing 100m from your center point.
Now the second approach can me using significantLocationChanges
On making the app go background or terminated, we can just stop location manager for further updating locations and can call the startMonitoringSignificantLocationChanges
self.locationManager?.stopUpdatingLocation()
self.locationManager?.startMonitoringSignificantLocationChanges()
When the app is killed, the location is grabbed from didFinishLaunching method's launchOptions?[UIApplicationLaunchOptionsKey.location]
if launchOptions?[UIApplicationLaunchOptionsKey.location] != nil {
//You have a location when app is in killed/ not running state
}
Make sure to keep BackgroundModes On for Location Updates
Also make sure to ask for locationManager?.requestAlwaysAuthorization() by using the key
<key>NSLocationAlwaysUsageDescription</key>
<string>Allow location</string>
in your Info.plist
There can be a third solution by taking 2 LocationManagers simultaneously.
For region
Significant Location Changes
As using significantLocationChanges
Apps can expect a notification as soon as the device moves 500 meters
or more from its previous notification. It should not expect
notifications more frequently than once every five minutes. If the
device is able to retrieve data from the network, the location manager
is much more likely to deliver notifications in a timely manner.
as per the give Apple Doc
So it totally depends on your requirements as the location fetching depends on many factors like the number of apps opened, battery power, signal strength etc when the app is not running.
Also keep in mind to always setup a region with good accuracy.
I know that this will not solve your problem completely but you will get an idea to move forward as per your requirements.

Is it possible to defer location updates when monitoring significant location changes?

I'm developing an app that monitors significant location changes to get the user's location in the background. I've successfully implemented locationManager.startMonitoringSignificantLocationChanges and the locationManager:didUpdateLocations and locationManager:didFailWithError methods of my CLLocationManagerDelegate.
However, SLC is actually more accurate than I need. According to Apple's docs - and corroborated by my tests - slc triggers a location update roughly every 500m and between 5 and 10 minutes. Therefore, I implemented locationManager.allowDeferredLocationUpdatesUntilTravelled:timeout in my delegate's didUpdateLocations method, as described in this guide: http://apple.co/1W4gqEJ.
Here's my code:
var deferringUpdates = false
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
for location in locations {
NSLog("Significant location change recorded:\n%#", location)
}
if let location = locations.first {
let secondsAgo: NSTimeInterval = location.timestamp.timeIntervalSinceNow
// Only process the location if it is very recent (less than 15 seconds old).
if abs(secondsAgo) < 15.0 {
saveExtendedUserInfo(withLocation: location)
}
}
if !deferringUpdates {
manager.allowDeferredLocationUpdatesUntilTraveled(810, timeout: 600)
deferringUpdates = true
NSLog("Deferring location updates...")
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
NSLog(error.localizedDescription)
}
func locationManager(manager: CLLocationManager,
didFinishDeferredUpdatesWithError error: NSError?)
{
deferringUpdates = false
if let deferralError = error {
NSLog(deferralError.localizedDescription)
}
}
Unfortunately, the the location manager never defers the updates. Immediately after allowDeferredUpdatesUntilTravelled:timeout is called, the delegate executes didFinishDeferredUpdatesWithError and produces kCLErrorDomain 12, which is CLError.DeferredNotUpdatingLocation.
Why am I getting that error? It seems to mean that the deferred update service doesn't recognize monitoring significant location changes as "updating location". Is it possible to defer the delivery of slc events, or somehow reduce their frequency? If so, how?
The purpose of deferring updates is to save battery consumed by the main CPU when processing 1 Hz location updates from the GPS. With deferred updates the CPU stays asleep saving battery while the GPS chip accumulates GPS locations once per second (1 Hz).
With Significant Location Change (SLC) the system is not using the GPS. It is determining location based on cell tower triangulation, which does not wake up the CPU until a significant change has happened.
These two feature are mutually exclusive, you can't defer Significant Location Change's updates because the GPS chip is not involved in SLC.

didUpdateLocations delegate method is repeated a random number of times

I get latitude and longitude of the user in my method didUpdateLocations. If location is allowed I call a method that takes in parameters latitude, longitude, and calls a webservice, else I display an UIAlertView.
Problem is: iOS calls my locationManager delegate method a random number of times. So my webservice is called several times... How can I fix it please?
When I call the location, verify if is allowed... I make the request in the previous screen:
// GET LOCATION
self.initializeWaitingScreen()
if( CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedWhenInUse ||
CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedAlways){
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.startUpdatingLocation()
} else {
let loginFailAlert: UIAlertView = UIAlertView(title: "Localisation refusée", message: "Vos fonctionnalités sont restreintes, pour accéder à l'application complète, veuillez activer la localisation", delegate: self, cancelButtonTitle: "OK")
loginFailAlert.show()
self.initializeUIComponent()
self.initializeDataWithWebServiceWithoutLocation()
}
My locationManager method:
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var lon = manager.location.coordinate.longitude.description
var lat = manager.location.coordinate.latitude.description
self.locationManager.stopUpdatingLocation()
self.initializeDataWithWebServiceWithLocation(lon, _lat: lat)
self.initializeUIComponent()
}
self.initializeDataWithWebServiceWithLocation(lon, _lat: lat)
take longitude and latitude, and give it to my method who call webservices.
This is expected behavior. As CLLocationManager determines the user's location, updates will be sent (I don't just mean the obvious). See this excerpt from Apple's docs:
Regardless of which location service you use, location data is reported to your app via the location manager’s associated delegate object. Because it can take several seconds to return an initial location, the location manager typically delivers the previously cached location data immediately and then delivers more up-to-date location data as it becomes available. Therefore it is always a good idea to check the timestamp of any location object before taking any actions. If both location services are enabled simultaneously, they deliver events using the same set of delegate methods.
If you require some filtering of events, you'll need to (1) ensure you've set your desiredAccuracy properly to help minimize the number of events then (2) perform any particular app-specific filtering. Be cautious though, since the reason you get multiple updates is that the determined location has changed. If you second-guess the system, you may wind up with inaccurate data.
Finally, evaluate whether you need location changes or significant location changes. If you don't need the high granularity, go with "significant".

Best Reactive-Cocoa approach for writing a CLLocationManagerDelegate, which will infrequently fetch location

Background
I'm really excited by the ReactiveCocoa framework and the potential it has, so I've decided that I'm going to bite the bullet and write my first app using it.
In my app, I've already written the various services and delegates, but I now need to 'Reactive-Cocoa-ise' them so that I can get on with actual GUI side of things.
That said, so that I better understand this, I'm writing a simple bit of code just to try out concepts.
In this case, writing a wrapper for CLLocationManagerDelegate.
In the actual app, the use case would be this:
1) When the app is loaded up (viewDidLoad) then 2) Attempt to fetch
the location of the device by
2.1) if location services not enabled then
2.1.1) check authorisation status, and if allowed to startMonitoringSignificantLocationChanges,
2.1.2) else return an error
2.2) else (location services are enabled)
2.2.1) if the location manager last location was 'recent' (6 hours or less) return that
2.2.2) else startMonitoringSignificantLocationChanges
3) when returning the location (either straight away, or via
locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations), then we also stopMonitoringSignificantLocationChanges
4) If code that calls on the LocationManagerDelegate receives and error, then ask the delegate for a fixed 'default' value
5) Next piece of code then uses the location (either fetched, or default) to go off and do a bunch of calls on third party services (calls to WebServices etc)
You can see a test harness at https://github.com/ippoippo/reactive-core-location-test
Concerns
The harness 'works' in that it goes off and fetches the location. However, I'm really concerned I'm doing this in the right way.
Consider this code
RACSignal *fetchLocationSignal = [lmDelegate latestLocationSignal];
RACSignal *locationSignal = [fetchLocationSignal catch:^RACSignal *(NSError *error) {
NSLog(#"Unable to fetch location, going to grab default instead: %#", error);
CLLocation *defaultLocation = [lmDelegate defaultLocation];
NSLog(#"defaultLocation = [%#]", defaultLocation);
// TODO OK, so now what. I just want to handle the error and
// pass back this default location as if nothing went wrong???
return [RACSignal empty];
}];
NSLog(#"Created signal, about to bind on self.locationLabel text");
RAC(self.locationLabel, text) = [[locationSignal filter:^BOOL(CLLocation *newLocation) {
NSLog(#"Doing filter first, newLocation = [%#]", newLocation);
return newLocation != nil;
}] map:^(CLLocation *newLocation) {
NSLog(#"Going to return the coordinates in map function");
return [NSString stringWithFormat:#"%d, %d", newLocation.coordinate.longitude, newLocation.coordinate.latitude];
}];
Questions
1) How do I handle errors? I can use catch which then gives me the opportunity to then ask the delegate for a default location. But, I'm then stuck on how to then pass back that default location as a signal? Or do I just change my defaultLocation method to return a RACSignal rather than CLLocation??
2) When I return the location, should it be done as sendNext or sendCompleted? Currently it's coded as sendNext, but it seems like something that would be done as sendCompleted.
3) Actually, does the answer to that depend on how I create the Delegate. This test app creates a new Delegate each time the view is loaded. Is that something I should do, I should I make the Delegate a singleton. If it's a singleton, then sendNext seems the right thing to do. But if would then imply that I move the code that I have in latestLocationSignal in my delegate into an init method instead?
Apologies for the rambling question(s), just seem to be going around in circles in my head.
Updating the answer above to ReactiveCocoa 4 and Swift 2.1:
import Foundation
import ReactiveCocoa
class SignalCollector: NSObject, CLLocationManagerDelegate {
let (signal,sink) = Signal<CLLocation, NoError>.pipe()
let locationManager = CLLocationManager()
func start(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
for item in locations {
guard let location = item as CLLocation! else { return }
sink.sendNext(location)
}
}
And to listen the events:
var locationSignals : [CLLocation] = []
signal.observeNext({ newLocation in
locationSignals.append(newLocation)
})
How do I handle errors? I can use catch which then gives me the opportunity to then ask the delegate for a default location.
You're so close. Use +[RACSignal return:] to create a signal which just sends your default location:
RACSignal *locationSignal = [fetchLocationSignal catch:^RACSignal *(NSError *error) {
NSLog(#"Unable to fetch location, going to grab default instead: %#", error);
CLLocation *defaultLocation = [lmDelegate defaultLocation];
NSLog(#"defaultLocation = [%#]", defaultLocation);
return [RACSignal return:defaultLocation];
}];
When I return the location, should it be done as sendNext or sendCompleted? Currently it's coded as sendNext, but it seems like something that would be done as sendCompleted.
In order to actually send data, you'll need to use -sendNext:. If that's the only thing that the signal is sending, then it should also -sendCompleted after that. But if it's going to continue to send the location as accuracy improves or location changes, then it shouldn't complete yet.
More generally, signals can send any numbers of nexts (0*) but can only complete or error. Once completion or error is sent, the signal's done. It won't send any more values.
Actually, does the answer to that depend on how I create the Delegate. This test app creates a new Delegate each time the view is loaded. Is that something I should do, I should I make the Delegate a singleton. If it's a singleton, then sendNext seems the right thing to do. But if would then imply that I move the code that I have in latestLocationSignal in my delegate into an init method instead?
I'm not sure I have enough context to answer this, except to say that singletons are never the answer ;) Creating a new delegate each time the view is loaded seems reasonable to me.
It's hard to answer broad questions like this because I don't know your design nearly as well as you do. Hopefully these answers help a bit. If you need more clarification, specific examples are really helpful.
Seems to be super easy in ReactiveCocoa 3.0 and swift :
import ReactiveCocoa
import LlamaKit
class SignalCollector: NSObject, CLLocationManagerDelegate {
let (signal,sink) = Signal<CLLocation, NoError>.pipe()
let locationManager = CLLocationManager()
func start(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
for item in locations {
if let location = item as? CLLocation {
sink.put(Event.Next(Box(location))
}
}
}
}
And wherever you need to listen to the location events, use the signal with an observer :
var locationSignals : [CLLocation] = []
signal.observe(next: { value in locationSignals.append(value)})
Note that since it's pre-alpha at the moment the syntax is likely to change.

Resources