TL/DR:
Anytime you stop displaying an MKMapView
in UITableViewCell
don’t forget to set showsUserLocation = false
.
At Rover, we have started taking advantage of the benefits of having a native app to create features like GPS tracked walks. These Rover Cards are great for owners to see the route taken on a dog walk, or verify that a sitter visited your pet during the day. As you can imagine, continuous GPS tracking can have a real impact on our providers’ batteries over the course of the day. Lately, we have been spending a good amount of time investigating how to efficiently use the phone’s battery while tracking walks.
We recently came across a surprising bug that would keep the GPS on after tracking a walk. We thought it would be good to share hoping that no one else leaves their user’s GPS on while not displaying a map.
The goal of the original project was to track the walker’s dog walk. We showed a map to indicate we are currently tracking their location. We decided that simply putting a MKMapView
inside a UITableViewCell
would be a great solution. When you turn on showsUserLocation
the map will refresh with the user location. We set the MKMapView
delegate to our table view cell and recentered the region when a new location gets updated. Problem solved right?
During our investigation we noticed in Instruments that after stopping a walk the GPS was still on, even though we turned off tracking in CLLocationManager
and removed the MKMapView
from the table.
Althoughthe cell was correctly being removed from the tableview, the GPS was still tracking and notifying our cell of location changes. Using the XCode memory debugger, we found the cell and discovered that the UITableView
kept a reference to the cell for reuse even though we removed our references. This reference meant that we were still getting delegate updates from func mapView(_ mapView: MKMapView, didUpdate location: MKUserLocation)
.
To make matters worse, because this view controller was stored in a UITabBarController
we always kept a reference to the view controller and would accidentally leave the GPS on for the duration of the session. :(
The actual fix is relatively simple. I would have hoped the OS would automatically set showsUserLocation = false
when the MKMapView
is removed from the view hierarchy.
Implement the didEndDisplaying
delegate method on UITableView
and turn off showsUserLocation
for the map cell.
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let cell = cell as? CurrentLocationCell {
cell.mapView?.showsUserLocation = false
}
super.tableView(tableView, didEndDisplaying: cell, forRowAt: indexPath)
}
And of course, don’t forget to turn it back on if the cell gets dequeued again!
With a few simple lines, we were able to reduce the battery consumed after walks are completed.
Want to work on interesting project like Rover Cards? We’re hiring!