Why does logging backgroundTimeRemaining show a wrong/big number even though app was moved to background? - ios

I'm logging my UIApplication.shared.backgroundTimeRemaining but the number is huge. It's almost 200 digits.
This is how I'm logging it.
os_log("Lat: %f | Long: %f | RemainingTime: %f ", log: log, type: .default, location.coordinate.latitude, location.coordinate.longitude, UIApplication.shared.backgroundTimeRemaining)
I thought there is something wrong with the format of my logging so I also tried placing a breakpoint and printing it but still the number that it logs is the same huge number. I also looked into this question which has a fair explanation, that is if your app is in foreground then the time would be that huge. But I still see this number even if there has been 5 minutes since I've moved the app to background.
A sample number I get for my remainingTime is:
179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000
Entire code:
import UIKit
import CoreLocation
import os.log
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate{
lazy var locationManager : CLLocationManager = {
var manager = CLLocationManager()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
manager.distanceFilter = 1
manager.pausesLocationUpdatesAutomatically = true
manager.allowsBackgroundLocationUpdates = true
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()
return manager
}()
var lastLocation : CLLocation?
var mapView : MKMapView?
let log = OSLog(subsystem: "XYZ.LocationAppSubSystem", category: "dumbo")
override func viewDidLoad() {
super.viewDidLoad()
if locationManager.location != nil{
}else {
DispatchQueue.main.async {
self.locationManager.startUpdatingLocation()
}
}
os_log("view was Loaded", log: log, type: .error)
mapView = MKMapView(frame: UIScreen.main.bounds)
mapView?.showsUserLocation = true
view.addSubview(mapView!)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
lastLocation = location
// let date = Date().description(with: Locale.current)
os_log("Lat: %{public}f | Long: %{private}f | RemainingTime: %{public}f ", log: log, type: .default, location.coordinate.latitude, location.coordinate.longitude, UIApplication.shared.backgroundTimeRemaining)
}
func locationManagerDidPauseLocationUpdates(_ manager: CLLocationManager) {
os_log("locationManager was paused", log: log)
let location = lastLocation
os_log("Lat: %{public}f | Long: %{private}f | RemainingTime: %{public}f ", log: log, type: .default, (location?.coordinate.latitude)!, (location?.coordinate.longitude)!, UIApplication.shared.backgroundTimeRemaining)
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
os_log("Region was exited", log: log)
}
func createRegion(location: CLLocation) {
let radius = 3.0
let region = CLCircularRegion(center: location.coordinate, radius: radius, identifier: "didPauseLocationUpdates")
region.notifyOnExit = true
region.notifyOnEntry = false
locationManager.startMonitoring(for: region)
}
func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) {
if region?.identifier == "didPauseLocationUpdates"{
os_log("Main Region was Failed to be created", log: log)
}else{
os_log("Other regions were checked ", log: log)
}
}
}

It is because even though the app is in foreground or even closed, when a didEnterRegion, didExitRegion (apparently didUpdateLocations as well) event occurs, the app gets 10 seconds. In those 10 seconds, the backgroundTimeRemaining value will show the same as in foreground. However, if you register for a background task, the backgroundTimeRemaining will show the real background time remaining after those 10 seconds have passed.
In iOS, regions associated with your app are tracked at all times, including when the app isn’t running. If a region boundary is crossed while an app isn’t running, that app is relaunched into the background to handle the event. Similarly, if the app is suspended when the event occurs, it’s woken up and given a short amount of time (around 10 seconds) to handle the event. When necessary, an app can request more background execution time using the beginBackgroundTaskWithExpirationHandler: method of the UIApplication class.
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/LocationAwarenessPG/RegionMonitoring/RegionMonitoring.html

Related

Get user location in all states (i.e backgroud, foreground or app terminated or killed)

I am developing ios app using Swift4 and in that, I need to fetch user location continuously after few seconds in all states i.e. background, foreground or even app is killed. I tried a lot of codes available but none seems work.
Capture location in all states
For app delegate :
if ((launchOptions?[UIApplication.LaunchOptionsKey.location]) != nil) {
print ("abhishek testing termination")
fromTerminated = true
locationManager.requestAlwaysAuthorization()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.allowsBackgroundLocationUpdates = true
locationManager.startUpdatingLocation()
locationManager.startMonitoringSignificantLocationChanges() //THIS IS WHERE THE MAGIC HAPPENS
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if(fromTerminated)
{
let newlocation : CLLocation = locations.last!
let locValue:CLLocationCoordinate2D = newlocation.coordinate
// let theaccuracy : CLLocationAccuracy = newlocation.horizontalAccuracy
print ("background location\(locValue.latitude)")
print ("terminated location\(locValue.longitude)")
// self.postdatatoserver()
}
}

Is that possible to receive Location even when app is not running in Swift

I am making an app that trace all the trips made by a user.
The app works perfectly when it is in background or in foreground but I am looking for a way to get it worked even if the app is not running.
I followed exactly this answer : Receiving Location even when app is not running in Swift
But this doesn't work as expected.
I have two property in my AppDelegate
var lastLocation:CLLocation?
//And a location manager
var locationManager = CLLocationManager()
In my DidFinishLaunchingWithOptions I configure the CLLocationManager
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
In my applicationWillTerminate :
lastLocation = HomeController.locations.last
createRegion(location: lastLocation)
Here is the rest :
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
UserDefaults.standard.set(UserDefaults.standard.integer(forKey: "nbLocNotrunning") + 1, forKey: "nbLocNotrunning")
//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")
}
}
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()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if UIApplication.shared.applicationState != .inactive {
} 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)
}
}
I wonder where the mistake can come from and why I never enter in the function didExitRegion.
For now, I just increment a variable in UserDefaut "nbLocNotrunning" in order to know how many times I leave a region but this number never increase when I stop my app.

How to check if user's current location is inside a location circle

i want to check if user if inside a vicinity. For example i have specified a radius of 50 meters around current of location of user. Let's say if user is moving, now i want to check if user in inside 50 meter radius or not. Here is my code
override func viewDidLoad() {
super.viewDidLoad()
locationManager.startMonitoringVisits()
locationManager.delegate = self
locationManager.distanceFilter = 1
locationManager.allowsBackgroundLocationUpdates = true
locationManager.startUpdatingLocation()
}
Here is code for checking distance
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {
return
}
let officeLocation = CLLocationCoordinate2D.init(latitude: 31.471303736482234, longitude: 74.27275174139386)
let circle = MKCircle(center: officeLocation, radius: 50 as CLLocationDistance)
if location.distance(from: officeLocation) > circle.radius {
self.newVisitReceived(des: "YOU ARE OUT OF OFFICE")
}
else{
self.newVisitReceived(des: "YOU ARE IN OFFICE")
}
}
Even if i don't move this code sends notification "YOU ARE OUT".
I would solve this with Geofences...
You have to specify a coordinate center & radius where you want to listen to the user when he goes inside/outside from your geofence.
override func viewDidLoad() {
super.viewDidLoad()
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.allowsBackgroundLocationUpdates = true
locationManager.requestAlwaysAuthorization()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedAlways || status == .authorizedWhenInUse {
// CLLocationCoordinate2D; You have to put the coordinate that you want to listen
let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: 324234, longitude: 23423), radius: 50, identifier: "Ur ID")
region.notifyOnExit = true
region.notifyOnEntry = true
manager.startMonitoring(for: region)
}
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
// User has exited from ur regiom
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
// User has exited from ur region
}
I hope this will be useful
The general problem with location services is that the measurements vary in accuracy, depending on a lot of factors. How would your like your code to behave if the user is standing right on the 50 meter boundary? Your current code would then flip back and forth randomly between 'in office' and 'out of office', if the accuracy is bad.
I think accuracy of a GPS is actually more than 4 meters under the best conditions, so a distanceFilter of 1 might not be appropriate.
I guess you would need some state in your app that tracks when the user was last seen inside the 50 meter radius, and also some grace period before updating that variable again, to avoid 'flickering'.

Location does not update after application comes back from background

I am building an app where I want to keep track of updated user location whenever the app comes back from background.
I wrote my location tracking code in AppDelegate's didFinishLaunchingWithOptions method
//Core Location Administration
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 70
locationManager.requestAlwaysAuthorization()
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.startMonitoringVisits()
locationManager.delegate = self
Since I was not able to validate Visits, I added the standard location tracking too
locationManager.allowsBackgroundLocationUpdates = true
locationManager.startUpdatingLocation()
I created CLLocationManagerDelegate block and added the following code
extension AppDelegate: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didVisit visit: CLVisit) {
let clLocation = CLLocation(latitude: visit.coordinate.latitude, longitude: visit.coordinate.longitude)
// Get location description
AppDelegate.geoCoder.reverseGeocodeLocation(clLocation) { placemarks, _ in
if let place = placemarks?.first {
let description = "\(place)"
self.newVisitReceived(visit, description: description)
}
}
}
func newVisitReceived(_ visit: CLVisit, description: String) {
let location = Location(visit: visit, descriptionString: description)
LErrorHandler.shared.logInfo("\(location.latitude), \(location.longitude)")
UserModal.shared.setUserLocation(location)
UserModal.shared.userLocationUpdated = Date()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {
return
}
locationManager.stopUpdatingLocation()
let uL = Location(location:location.coordinate, descriptionString: "")
LErrorHandler.shared.logInfo("\(uL.latitude), \(uL.longitude)")
UserModal.shared.setUserLocation(uL)
UserModal.shared.userLocationUpdated = Date()
}
}
I added this code to begin location tracking when the app comes to foreground
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
locationManager.startUpdatingLocation()
}
If I am on a ViewController and the application goes back to the background, it does not refresh the location when the app comes to foreground.
Can someone suggest a better way to do this?
You should write code for location in didbecomeActive method of App delegate not in didFinishLaunchingWithOptions. Try this hope it will help.

Location Manager didEnterRegion and didExitRegion methods are not called

I have been doing some iOS development for a couple of months and recently I am developing a bus app.
I am currently mimicking the bus' movements and set up multiple annotations on the bus stops. For test purposes, I have setup just one bus stop and am trying to monitor when the bus has entered this region and exited as well.
Strangely, my didStartMonitoringForRegion method is called perfectly but neither the didEnterRegion nor didExitRegion methods are called. Along with this, no errors are called whatsoever. Every time I run the program, the bus pretty much passes the stop without prompting me so.
Could someone explain to me why this is happening and how to resolve it?
// ViewController.swift
// Parta Service
// Created by Emil Shirima on 8/24/15.
// Copyright (c) 2015 Emil Shirima. All rights reserved.
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
var allBusAnnotations = [MKPointAnnotation]()
var summitEastBusStations = [CLLocationCoordinate2D]()
var busStopNames = ["Dix Stadium", "Risman Plaza", "Terrace Drive", "Terrace Drive 2","C-Midway","Theatre Dr.","East Main Street","South Lincoln"]
var radius = 250 as CLLocationDistance
// 0.02 is the best zoom in factor
var mapZoomInFactor : Double = 0.02
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.getBusStop()
self.locationManager.delegate = self
// gets the exact location of the user
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
// gets the user's location only when the app is in use and not background
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.mapView.showsUserLocation = true
self.setBusStopAnnotations(summitEastBusStations)
self.canDeviceSupportAppBackgroundRefresh()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
// sends the latitude and longitude to the Apple Servers then returns the address
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: { (placeMarks: [AnyObject]!, error: NSError!) -> Void in
if error != nil
{
println("Reverse Geocode Failed: " + error.localizedDescription)
println("The real reason is " + error.localizedFailureReason!)
return
}
if placeMarks.count > 0
{
// gets the most updated location
let pm = placeMarks.last as! CLPlacemark
let centre = CLLocationCoordinate2D(latitude: manager.location.coordinate.latitude, longitude: manager.location.coordinate.longitude)
// draws a circle in which the map will zoom to
let region = MKCoordinateRegion(center: centre, span: MKCoordinateSpan(latitudeDelta: self.mapZoomInFactor, longitudeDelta: self.mapZoomInFactor))
self.mapView.setRegion(region, animated: true)
self.displayLocationInfo(pm)
self.geoFencing()
}
})
}
func displayLocationInfo(placemark: CLPlacemark)
{
println("Current Location:")
println(placemark.name)
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
println("Location Manager Failed: " + error.localizedDescription)
}
func locationManager(manager: CLLocationManager!, didStartMonitoringForRegion region: CLRegion!) {
println("The monitored regions are: \(locationManager.monitoredRegions)")
}
func locationManager(manager: CLLocationManager!, monitoringDidFailForRegion region: CLRegion!, withError error: NSError!) {
println("Failed to monitor the stated region")
}
func locationManager(manager: CLLocationManager!, didEnterRegion region: CLRegion!) {
println("The bus has entered the region")
createAlert("Region Entry", alertMessage: "You have entered the specified region", alertCancelTitle: "OK")
}
func locationManager(manager: CLLocationManager!, didExitRegion region: CLRegion!) {
println("The bus has left the region")
createAlert("Region Exit", alertMessage: "You have exited the specified region", alertCancelTitle: "OK")
}
//TODO:Implement GeoFencing in order to know when the bus is at a station or not.
func geoFencing()
{
locationManager.requestAlwaysAuthorization()
let rismanPlaza = CLLocationCoordinate2D(latitude: 41.1469492, longitude: -81.344068)
var currentBusStop = CLLocation(latitude: rismanPlaza.latitude, longitude: rismanPlaza.longitude)
addRadiusCircle(currentBusStop)
let busStopRegion = CLCircularRegion(center: CLLocationCoordinate2D(latitude: rismanPlaza.latitude, longitude: rismanPlaza.longitude), radius: radius, identifier: busStopNames[1])
if radius > self.locationManager.maximumRegionMonitoringDistance
{
radius = self.locationManager.maximumRegionMonitoringDistance
}
if CLLocationManager.isMonitoringAvailableForClass(CLCircularRegion)
{
locationManager.startMonitoringForRegion(busStopRegion)
}
else
{
//TODO: Create an alert telling the user that region tracking for this area is not available
println("Monitoring is not available for this region")
}
}
// creates the radius around the specified location
func addRadiusCircle(location: CLLocation)
{
self.mapView.delegate = self
var circle = MKCircle(centerCoordinate: location.coordinate, radius: radius)
self.mapView.addOverlay(circle)
}
// performs the actual circle colouring
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer!
{
if overlay is MKCircle
{
var circle = MKCircleRenderer(overlay: overlay)
circle.strokeColor = UIColor.redColor()
circle.fillColor = UIColor(red: 255, green: 0, blue: 0, alpha: 0.1)
circle.lineWidth = 1
return circle
}
else
{
return nil
}
}
// removes the drawn circles from the map
func removeCircleLayers()
{
var layers = mapView.overlays
mapView.removeOverlays(layers)
}
func canDeviceSupportAppBackgroundRefresh()
{
if UIApplication.sharedApplication().backgroundRefreshStatus == UIBackgroundRefreshStatus.Available
{
createAlert("Background Refresh Status", alertMessage: "Background Updates are enable for the App", alertCancelTitle: "OK")
}
else if UIApplication.sharedApplication().backgroundRefreshStatus == UIBackgroundRefreshStatus.Denied
{
createAlert("Background Refresh Status", alertMessage: "Background Updates are disabled by the user for the App", alertCancelTitle: "OK")
}
else if UIApplication.sharedApplication().backgroundRefreshStatus == UIBackgroundRefreshStatus.Restricted
{
createAlert("Background Refresh Status", alertMessage: "Background Updates are restricted and the user can't do anything App", alertCancelTitle: "OK")
}
}
func createAlert(alertTitle: String, alertMessage: String, alertCancelTitle: String)
{
let alert = UIAlertView(title: alertTitle, message: alertMessage, delegate: self, cancelButtonTitle: alertCancelTitle)
alert.show()
}
}
I ended up using the CLRegion.containsCoordinate(location.coordinate) method instead. It works pretty much the same way.
Once the object has entered my set region, it returns true and from here I can know when it's entered and exited the region.
If the user device is at current location(Latitude and longitude) the didEnterRegion: and didExitRegion: delegates will not be triggering. Use didDetermineState: method to find whether the user/device is in current region that is moniotred. If it so, didDetermineState: will be triggered. Once the user leaves the region didExitRegion: will be triggered.

Resources