We have developed a construction project application in iOS, where a project manager can invite users to work in that construction project. Whenever the linked users/project manager of a particular project reaches the project site location we send them notifications to tell them to watch site safety videos before entering the site. If the users have seen those videos then we don’t send these notifications again to those users on next time entering the project location. This is all working good.
Now we have added a new functionality in this app with which attendance of these users can be marked as they enter in the project site location. For this we have used Geofencing feature, we create geofences for nearby projects of user location, as soon as the user enters a particular project geofence we mark him as checked in (with entry timing) and on exiting from the geofence, the user gets marked as checked out(with exit timings). This works 100% if we keep the iOS app in background (if app is kept open in recent apps), but it does NOT ALWAYS work when the app is closed (if the app is removed from recent apps). We have tried Significant Region monitoring as well, but not getting any consistency in the results for all the users.
So our question here is that does iOS support this GPS call when app is closed, means can we execute any code in the app when app is closed (removed from recent apps)? If yes then to what accuracy because our GPS code call does not work 100% when the app is closed?
My code with Geofencs is here :-
func locationManager(manager: CLLocationManager, monitoringDidFailForRegion region: CLRegion?, withError error: NSError) {
NSLog("Monitoring failed for region with identifier: \(region!.identifier) Error: \(error.description)")
if let region = region as? CLCircularRegion
{
self.locationMngr.stopMonitoringForRegion(region)
self.locationMngr.startMonitoringForRegion(region)
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
NSLog("Location Manager failed with the following error: \(error)")
}
func locationManager(manager: CLLocationManager!, didEnterRegion region: CLRegion!) {
println("Entered Region \(region.identifier)")
if let region = region as? CLCircularRegion
{
// Send region.identifier(Project ID) to server with User ID for CHECK-IN
}
}
func locationManager(manager: CLLocationManager!, didExitRegion region: CLRegion!) {
println("Exited Region \(region.identifier)")
if let region = region as? CLCircularRegion
{
// Send region.identifier(Project ID) to server with User ID for CHECK-OUT }
}
func locationManager(manager: CLLocationManager!, didStartMonitoringForRegion region: CLRegion!) {
NSLog("***Starting monitoring****** \(region.identifier)")
}
func locationManager(manager: CLLocationManager!, didDetermineState state: CLRegionState, forRegion region: CLRegion!) {
if state == CLRegionState.Inside {
//// Send region.identifier(Project ID) to server with User ID for CHECK-IN
}else{
NSLog("STATE is OUT of region");
}
}
Related
(iOS 11, Swift 4.1)
I wrote a cordova plugin in swift to handle region monitoring, but it isn't working properly when my app is suspended or killed. Here is my class with the relevant functions:
class GeofenceManager : NSObject, CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
switch state {
case .inside:
log ("Did Enter Region: " + region.identifier)
self.postGeofenceTransition(region: region, transitionType: 1)
break
case .unknown:
log ("Unknown Transition for region: " + region.identifier)
// self.postGeofenceTransition(region: region, transitionType: 1)
break
default:
break
}
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
log ("Did Exit Region: " + region.identifier)
self.postGeofenceTransition(region: region, transitionType: 2)
}
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
log("Did start monitoring region: " + region.identifier)
self.locationManager.requestState(for: region)
}
}
(I do it this way to handle someone that's already in the region when we start monitoring)
I've also got "didChangeAuthorizationStatus" that fetches and sets all the geofences, that is working fine.
In my AppDelegate (objc) I'm attempting to start Location Services if the app is opened from a region event, with
if([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
_locationManager = [[CLLocationManager alloc] init];
[_locationManager setDelegate:self];
[_locationManager setDistanceFilter:kCLHeadingFilterNone];
//change the desired accuracy to kCLLocationAccuracyBest
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
//SOLUTION: set setPausesLocationUpdatesAutomatically to NO
[_locationManager setPausesLocationUpdatesAutomatically:NO];
[_locationManager startUpdatingLocation];
}
My question is, if my app is opened from an event, i.e. "didEnterRegion", I immediately start location services, but will I hit my "didEnterRegion" delegate again? And am I doing something wildly wrong here?
I think I'm missing some understanding of iOS application states, have dug into the docs but it hasn't clicked yet. Any insight is very appreciated.
My question is, if my app is opened from an event, i.e. "didEnterRegion", I immediately start location services, but will I hit my "didEnterRegion" delegate again?
If your app was suspended or terminated, and if it is awakened or launched just to receive didEnterRegion, it stays in the background. It is given time just to handle this one event and then it is suspended.
You cannot start location updates with startUpdatingLocation at that point. You are in the background! Even if you are authorized for background updates, you cannot start getting updates while you are in the background. And even if you could, your code would fail, because you never set allowsBackgroundLocationUpdates. And even if you could do that, you wouldn't get any events, because you have no implementation of didUpdateLocation.
The correct procedure is: do nothing. Just handle didEnterRegion and get out. If the user enters the region again, your didEnterRegion will be called again. Region monitoring just goes on forever until you stop it (and make sure you do stop it, or the user may be be forced to delete your app).
Addendum: How to respond to being launched from scratch in the background like this? You can detect the key in didFinishLaunchWithOptions and handle it there and return false, or you can ignore it and return true and receive didEnterRegion. But either way you must immediately create a location manager and appoint its delegate or you will get nothing, obviously. This is why you should always create the location manager and set its delegate in your app delegate or root view controller, something that always exists, and exists as early as possible in the life of the app, as soon as it launches.
I have implemented geofencing functionality in my App.
I get notified when user enter into particular region with below method
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if region is CLCircularRegion {
handleEvent(forRegion: region)
}
}
Above method is calling even in terminated state.
Is there any way to relaunch or activate application in terminated
state if enter into the region.
When I run my iOS app from Xcode to the iOS simulator or to my physical device, the app crashes within a couple of seconds. As the app then enters the background and I am returned to the iPhone homescreen, the Alert View asking for permission to use my location pops up but then quickly disappears before I can select an answer.
After declaring and initializing a CLLocationManager called "locationManager", I believe that the errors are triggered from these statements:
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
The main error that appears in the console logs is:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Delegate must respond to locationManager:didFailWithError:'
I have set my usage description with the "NSLocationWhenInUseUsageDescription", so that my app can present to the user what their location will be used for, thus giving the app permission to access their location. Is there anything else I am missing that may contribute to this error?
In order to get the location of the user, do I only need it request it through adding the "NSLocationWhenInUseUsageDescription" key to the Info.plist file, or are there more measures I need to approach?
To solve your problem:
Create a class like, and override the methods as below:
class LocationDelegate : NSObject, CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// here you will receive your location updates on `locations` parameter
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
// if something goes wrong comes to here
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// here you can monitor the authorization status changes
}
}
Create a global instance of this class wherever you want.
let locationDelegate: LocationDelegate = LocationDelegate()
Then before you ask permission set the delegate:
locationManager.delegate = locationDelegate
How do I get the current location of my iOS device?
It seems that Apple has made changes that to how get location and I could not find an up-to-date post. I answered this question myself below:
I couldn't find an up-to-date solution to getting the current location so here's how I did it.
My source is apple's sample code on location tracking and smart watch called PotLocCoreLocationwithiPhoneandAppleWatch: https://github.com/robovm/apple-ios-samples/tree/master/PotLocCoreLocationwithiPhoneandAppleWatch
Here's what you have to do in your app.
Note that everything from steps 2-6 are in this gist: https://gist.github.com/JonMercer/75b3f45cda16ee5d1e0955b2492f66dd
In the project settings in xcode, click on the capabilities tab (it's the tab beside the general tab where you put in your bundle identifier). Then turn on Background Mode and enable Location updates. EDIT thanks to #Rob: And also add NSLocationWhenInUseUsageDescription to your plist. The value can be anything you like
Inside your view controller, extend the CLLocationManagerDelegate
class YourClass: UIViewController, CLLocationManagerDelegate {
Implement these two functions
/**
Increases that location count by the number of locations received by the
manager. Updates the batch count with the added locations.
*/
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//use the "locations" variable here
}
/// Log any errors to the console.
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Error occured: \(error.localizedDescription).")
}
Create a let manager = CLLocationManager() variable
On viewDidLoad() set the following: manager.requestWhenInUseAuthorization(), manager.allowsBackgroundLocationUpdates = true, and manager.startUpdatingLocation()
In order to stop tracking do the following: manager.stopUpdatingLocation() and manager.allowsBackgroundLocationUpdates = false
I'm currently trying to find a way to range beacons in the background in iOS by using location monitoring and then triggering the ranging like so:
func locationManager(manager: CLLocationManager, didDetermineState state: CLRegionState, forRegion region: CLRegion) {
if (state == .Inside) {
locationManager.startRangingBeaconsInRegion((region as? CLBeaconRegion)!)
}
}
I'm then trying to get an API call to be made in the beacon ranging
func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) {
for beacon in beacons {
let minor = beacon.minor as Int
let major = beacon.major as Int
do {
try APICall.canSeeBeacons(major, minor: minor)
} catch {
print("Error making API call")
}
}
}
However this only works for about ten minutes while the phone is in the background, after ten minutes it no longer works but I'm hoping to make it continuous so that API calls can always be made when a beacon is found. I do also have the correct keys set in the permissions and I'm using requestAlwaysAuthorization() on my locationManager
Take a look on the Apple's iOS Dev Library regarding Background Executions:
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
If the type of your app is one of the accepted types for longer background executions, add the key Required background modes to your Info.plist with an array, containing one or more of the enabled types: audio, location, voip, newsstand-content, external-accessory and/or bluetooth-central.
Just note that this will be reviewed by Apple before publishing your app on the App Store.
EDITED:
I didn't use this for a while, but tested now and it seems it's even easier since Xcode 6.
Follow the steps:
1. Add the key Required background modes to your Info.plist
2. Go to Capabilities
3. Select the background mode(s) that fit.
I had the same problem. As said a commentator I've moved location manager delegate methods into the App Delegate. And also I added this:
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
And it works.