Is it possible to work with geolocation in iOS when the application is minimized and send data to the server?
However, the task is often more complex and since I have significant experience in this field, I decided to share this experience.
In order to have some specifics, I assumed that the task before us was to write a cycle tracker. From the user's side, it looks like this:
ps final code here .
For the development can be divided into 3 main areas of work.
To begin, consider the usual work with CLLocationManager .
import CoreLocation final class LocationService: NSObject, CLLocationManagerDelegate { private let locationManager = CLLocationManager() override init() { super.init() locationManager.delegate = self locationManager.startUpdatingLocation() } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { } }
But for this to work, you need to confirm the user that he allows the use of information about his location.
iOS provides 2 rights to work with geolocation:
From this you might think that working with geolocation in the background can only be done with requestAlwaysAuthorization rights - this is not so.
And in the same way, requestAlwaysAuthorization does not allow to work quietly in the background out of the box. It is about working with regions, popular places, significant movements and so on.
If the code above is what is in the project, then calling the requestWhenInUseAuthorization () method or requestAlwaysAuthorization () will not show the user a request for rights.
To do this, you also need to add explanatory text of the message to info.plist in the appropriate NSLocationAlwaysUsageDescription / NSLocationWhenInUseUsageDescription key
Now, after confirming the user rights, we can work with geolocation.
For an application to work with geolocation in the background, you need to do 2 things:
allowsBackgroundLocationUpdates = true
Everything, the application can work in the background with geolocation, as well as send network requests and so on.
As soon as the user, having minimized the application, will remain stationary for some time from the point of view of the system, geolocation will stop, and with it the application.
The thing is that by default CLLocationManager uses a pause for geolocation pausesLocationUpdatesAutomatically . And this option is not as simple as it seems.
And at some point it stops working.
As you understand, a pause started, and after a while the work of the application itself, which is in the background, stopped. Pretty logical, because it is a pause.
I continued driving, but the LocationManager is still on pause. And it will remain on a pause, as long as I do not deploy the application myself.
Thus, the system tries to save battery power in cases when it is necessary to work before the “stop”.
If, nevertheless, the application needs to work before the end of the movement, then it is possible to help the system better determine this state by specifying a suitable goal in the "activityType", for which we work with geolocation.
For us, this is a tangible problem, so we simply disable the pause for the LocationManager.
pausesLocationUpdatesAutomatically = false
Earlier, I mentioned the access rights to geolocation requestAlwaysAuthorization . And that this makes it possible to receive events of the CLLocationManager API . And to receive both being in the background, and in the unloaded state. In the case of the latter, the system can restart our application to deliver a new event. For example:
locationManager.startMonitoringSignificantLocationChanges() — locationManager.startMonitoringVisits() — locationManager.startMonitoring(for: CLRegion) —
This is what we will use. If the user is killing the application, then we need to get back to work as quickly as possible. In my case, the most suitable would be startMonitoringSignificantLocationChanges , since the regions have limitations in radius. The main thing is not to forget to start again to configure and run CLLocationManager .
import CoreLocation final class LocationService: NSObject, CLLocationManagerDelegate { private let locationManager = CLLocationManager() override init() { super.init() locationManager.delegate = self locationManager.requestAlwaysAuthorization() locationManager.allowsBackgroundLocationUpdates = true locationManager.pausesLocationUpdatesAutomatically = false locationManager.startUpdatingLocation() locationManager.startMonitoringSignificantLocationChanges() } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { } }
Fine! The application works in the background, the application is restarted, works with geolocation, can work with the network, and so. That's just to remove us with such activity from the device.
If you open the statistics of energy consumption, then our application with a probability of 99.99% will be the leader and unfortunately not in terms of savings. Therefore, we will now optimize.
The battery consumption is greatly affected by the required error from the CLLocationManager .
We can demand the most accurate data, but we can, with an error of about 10 meters, 3 kilometers, and so on ( kCLLocationAccuracy * ).
Appropriately, the higher the required data quality, the greater the battery consumption.
Therefore, when the error of 100m is enough for you, you do not need to take the maximum quality.
More interestingly, if you demand poor quality, then most likely the system will produce more than you expect. Therefore, it is imperative not to require an error better than you really need.
ps required does not mean valid.
Additionally, you can win in the fight for the battery, if you remember about distanceFilter and allowDeferredLocationUpdates .
We have indicated that our goal is a bicycle tracker. At the moment we are processing data always, regardless of whether we need it or not. Because of the pause, we can not stop geolocation.
One of the solutions will change the required data quality during the trip and at other times. The difference between the best and worst data is almost 4 times.
func setActiveMode(_ value: Bool) { if value { locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters locationManager.distanceFilter = 10 } else { locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers locationManager.distanceFilter = CLLocationDistanceMax } }
Now it remains only to track when the user rides a bicycle. For this we can use CMMotionActivityManager from CoreMotion .
motionManager.startActivityUpdates(to: .main, withHandler: { [weak self] activity in self?.setActiveMode(activity?.cycling ?? false) })
import CoreLocation import CoreMotion final class LocationService: NSObject, CLLocationManagerDelegate { private let locationManager = CLLocationManager() private let motionManager = CMMotionActivityManager() override init() { super.init() locationManager.delegate = self locationManager.requestAlwaysAuthorization() locationManager.allowsBackgroundLocationUpdates = true locationManager.pausesLocationUpdatesAutomatically = false setActiveMode(true) locationManager.startUpdatingLocation() locationManager.startMonitoringSignificantLocationChanges() motionManager.startActivityUpdates(to: .main, withHandler: { [weak self] activity in self?.setActiveMode(activity?.cycling ?? false) }) } func setActiveMode(_ value: Bool) { if value { locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters locationManager.distanceFilter = 10 } else { locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers locationManager.distanceFilter = CLLocationDistanceMax } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { } }
Type A: Maximum Quality
Type B: Maximum Quality + Filtration
Type C: Poor quality + filtering
Type D: Without Application
Is it possible to further improve? Of course. This approach is necessary if it is critical to process all geolocation in the background. Further depends on your imagination.
<key>NSMotionUsageDescription</key> <string>$(PRODUCT_NAME) motion use.</string>
The application must be checked. Getting off the writing process for a “trip” is not a good idea. Yes, and about debugging in this approach, you can forget.
Fortunately, Apple allows us to use GPX files (and we don’t even need a real device to work in this case).
We select the service that generates the route of the movement, and save the following file in gpx:
<?xml version="1.0"?> <gpx version="1.1" creator="Xcode"> <wpt lat="54.91148" lon="83.07381"/> <wpt lat="54.90792" lon="83.07243"/> </gpx>
Unfortunately, it is difficult to write what is interesting, what is obvious, so there are not many things to do:
ps final code here .
Source: https://habr.com/ru/post/275769/
All Articles