Here's the code I'm using - all in Swift 3.0.
This setup of the locationManager will take care of the distance filtering and accuracy that you're after:
lazy var locationManager: CLLocationManager = {
[unowned self] in
var _locationManager = CLLocationManager()
_locationManager.delegate = self
_locationManager.desiredAccuracy = [Your Value For trackingAccuracy - see below]
_locationManager.distanceFilter = [Your Value For the filter i.e., 250 for 250 meters]
_locationManager.allowsBackgroundLocationUpdates = true
_locationManager.pausesLocationUpdatesAutomatically = false
_locationManager.activityType = .fitness
return _locationManager
}()
The accuracy setting would be from the predefined set in location manager, e.g., kCLLocationAccuracyNearestTenMeters or kCLLocationAccuracyHundredMeters.
The option to allow background updates lets you get new points even if your app is not in the foreground. Turning off pauses automatically is necessary if the phone will stop moving from time to time - otherwise, it will automatically turn off capture if your user stops to rest for 5 minutes and won't turn it back on - you must reset these when you're done.
Authorization status should be dealt with in a separate check as follows so that you can request authorization if it's not already given:
if CLLocationManager.authorizationStatus() != .authorizedAlways // Check authorization for location tracking
{
locationManager.requestAlwaysAuthorization() // Will callbackdidChange... once user responds
} else {
locationManager.startUpdatingLocation()
}
Since there's a delay in getting authorization, if you have to make the request you'll need to wait to request it to start updating locations until you hear back from the locationManager, which you can do as follows:
@nonobjc func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status
{
case .authorizedAlways:
locationManager.startUpdatingLocation()
default:
[Whatever you want to do - you can't get locations]
}
}
I need .authorizedAlways, you may be able to get away with .authorizedWhenInUse depending on your use case.
I add an additional filter for accuracy based on checking the accuracy fields in the positions passed back to me by the locationManager. Note that there's separate horizontal and vertical accuracy values (confidence distance in meters); if you want to use the elevation value you need to pay attention to the second accuracy value as elevation is inherently less accurate in an iPhone.
I also check that the new point was captured after the previous point - out of sequence values come through sometimes and indicate "troubled" values. I've read that you may get accuracy values less than 0 to indicate problems, so you may want to check for that before using the position, though I haven't seen that issue. Here's the code:
// Called back by location manager - passes arrays of new CLLocation points captured. With distanceFilter set to 250, this will get called for a change of 250M. You'll want to save the value for your timed pushLocation function.
// This function needs to be trim as it continues to be called when TrailHead is in the background and, if
// we take too long we'll get killed by iOS.
var savePosition: CLLocationCoordinate2D?
private var latestTimeProcessed = Date() // Initialize to ensure any point accepted received after initialization
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
for location in locations {
if latestTimeProcessed.compare(location.timeStamp) == .orderedAscending // Out of seq indicates a locationManager problem - discard
&& location.horizontalAccuracy < [Whatever accuracy limit you want to use] // Applying a user-selected quality filter
{
latestTimeProcessed = location.timeStamp // Used to discard any earlier timestamped points returned by the location manager
[PROCESS THE POSITION HERE - E.G.]
savePosition = locations.last
}
}
}
As far as doing the push updates, I'd add a timer
private var myTimer: Timer? = nil
private var longInterval = 900.0 // 900 seconds = 15 minutes
override func viewDidLoad()
{
....
myTimer = Timer(timeInterval: timerInterval, target: myself, selector: #selector(myself.pushLocation), userInfo: nil, repeats: true)
RunLoop.current.add(myTimer!, forMode: RunLoopMode.commonModes)
....
}
pushLocation(lat:double,long:double){
[Use savePosition.latitude, savePosition.longitude]
}
Hope this helps...