I have implemented geofencing to detect the entry and exit from the region. It seems working fine when the app is in a foreground/background/terminated state initially. I am testing this functionality using GPX. When an app is terminated, I am getting entry exit notifications too. But I have observed that in many scenarios when an app is suspended or terminated for a longer period of time, Even though the user is entering and leaving the region, No notifications are triggered. When I open the app manually, I can see the entry,or exit notifications instantly.
Here is my code snippet.
class LocationService: NSObject, CLLocationManagerDelegate {
static let sharedInstance: LocationService = { LocationService()
}()
var locationManager: CLLocationManager?
var startLocation: CLLocation?
var lastLocation: CLLocation?
var delegate: LocationServiceDelegate?
var isAuthorized: ((Bool) -> Void)?
var boolSendUpdate = false
var locationTimer = Timer()
var isFirstTime:Bool!
override init() {
super.init()
self.locationManager = CLLocationManager()
guard let locationManager = self.locationManager else {
return
}
//locationManager.desiredAccuracy = kCLLocationAccuracyBest // The accuracy of the location data
locationManager.delegate = self
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.allowsBackgroundLocationUpdates = true// if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self){
// print("Available")
// }
//
NotificationCenter.default.addObserver(self, selector:#selector(startUpdatingLocation), name: UIApplication.didBecomeActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector:#selector(stopUpdatingLocation), name: UIApplication.willTerminateNotification, object: nil)
}
func checkPermission(isAuthorized: ((Bool) -> Void)? = nil) {
guard let locationManager = self.locationManager else {
return
}
switch(CLLocationManager.authorizationStatus()) {
case .authorizedAlways,.authorizedWhenInUse:
self.startUpdatingLocation()
isAuthorized?(true)
// get the user location
case .restricted, .denied:
isAuthorized?(false)
// redirect the users to settings
popupAlert(title: NSLocalizedString("settings", comment: ""), message:go_to_settings, actionTitles: [NSLocalizedString("Cancel", comment: ""),NSLocalizedString("Settings", comment: "")], actions:[{action1 in
},{action2 in
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in })
}
}, nil])
case .notDetermined:
isAuthorized?(false)
locationManager.requestWhenInUseAuthorization()
@unknown default:
isAuthorized?(false)
locationManager.requestWhenInUseAuthorization()
}
}
@objc func startUpdatingLocation() {
self.locationManager?.startUpdatingLocation()
self.locationManager?.requestAlwaysAuthorization()
}
@objc func stopUpdatingLocation() {
if !CLLocationManager.significantLocationChangeMonitoringAvailable() {
return
}
self.locationManager?.stopUpdatingLocation()
self.locationManager?.startMonitoringSignificantLocationChanges()
}
func setUpGeofenceing(location:CLLocation,identifier:String = "",radius:CLLocationDistance,status:enumShiftStatus) {
let geofenceRegionCenter = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let geofenceRegion = CLCircularRegion.init(center: geofenceRegionCenter, radius: radius, identifier: identifier)
geofenceRegion.notifyOnExit = true
geofenceRegion.notifyOnEntry = true
if !CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
print("Geofencing is not supported on this device!")
UIApplication.shared.windows.first?.rootViewController?.presentAlert(withTitle:"MetroOne Officer", message: "Geofencing is not supported on this device!")
return
}
if locationManager?.monitoredRegions.contains(geofenceRegion) == false {
locationManager?.startMonitoring(for: geofenceRegion)
}
}
func stopGeoFenceing(identifier: String = ""){
}
// CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard let delegate = self.delegate else {
return
}
delegate.didChangeAuthorization(status: status)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
// singleton for get last location
self.lastLocation = location
// use for real time update location
updateLocation(currentLocation: location)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
// do on error
updateLocationDidFailWithError(error: error as NSError)
}
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
debugPrint("Started Monitoring for Region:::",region.description)
guard #available(iOS 14, *) else {
self.locationManager?.requestState(for:region)
return
}
}
func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) {
debugPrint("error::\(error.localizedDescription)")
}
// Private function
private func updateLocation(currentLocation: CLLocation){
guard let delegate = self.delegate else {
return
}
delegate.tracingLocation(currentLocation: currentLocation)
}
private func updateLocationDidFailWithError(error: NSError) {
guard let delegate = self.delegate else {
return
}
delegate.tracingLocationDidFailWithError(error: error)
}
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
switch state {
case .inside:
postApiGeoFenceEntries(type: RegionType.In.rawValue, shiftStatus: enumShiftPostStatus.CheckIn.rawValue)
case .outside:
postApiGeoFenceEntries(type: RegionType.Out.rawValue, shiftStatus: enumShiftPostStatus.CheckOut.rawValue)
case .unknown:
print("Unknown")
default:
print("Default")
}
}
}
App delegate code snippet.
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch
if launchOptions?[UIApplication.LaunchOptionsKey.location] != nil {
_ = LocationService.sharedInstance
UNUserNotificationCenter.current().delegate = self
}
return true
}
}
I have also enabled location updates in capabilities. Please let me know if I am missing something.
Thank you.