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?

Current Location Tracking

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.

Party Time

Want to work on interesting project like Rover Cards? We’re hiring!