There are tons of great tutorials online for using CoreLocation. Apple even provides some sample code. So why am I bothering to write this article? Because all the existing tutorials focus on getting the user’s longitude and latitude, but none actually show you how to get the user’s travelled distance or speed. With that in mind, here are the main goals for the project we’ll be creating:
- Determine the user’s travelled distance
- Determine the user’s current speed
- Determine GPS “signal” strength
- Generate waypoints that can be saved and later used to recreate a user’s path
Heads up: I’m assuming you are comfortable with Objective C and iOS development. If not, Apple has provided a great starting point for you here. You should really be comfortable with all that before proceeding.
Alright, let’s dig right in. This isn’t going to be a tutorial, per se. Instead, I’m going to just go over some of the highlights and methodology behind my PSLocationManager class. You can follow along on the GitHub project here:
https://github.com/perspecdev/PSLocationManager
One quick prerequisite: PSLocationManager uses CoreLocation, and it’s easy to forget to add the framework to your project.

CLLocationManager does most of the heavy lifting, so you really need to be familiar with it. The documentation can be found here. One important thing to keep in mind is that CLLocationManager doesn’t ping your app at some regular interval with location updates. Instead, you provide CLLocationManager with a delegate, and the delegate methods are only called when the user changes location.
Before writing any code, it’s important to take a close look at the goals for the project:
Determine the user’s travelled distance
CLLocationManager doesn’t provide any functionality for this, so PSLocationManager needs to calculate the travelled distance itself.
Determine the user’s current speed
CLLocationManager does provide a speed property, but I wanted to customize how and when speed is calculated (e.g. to average the user’s speed over some span of time, rather than just calculating the speed between the last two locations received).
Determine GPS “signal” strength
CLLocationManager doesn’t provide any indication of GPS “signal” strength.
Generate waypoints that can be saved and later used to recreate a user’s path
For this, the CLLocation objects passed to CLLocationManager’s delegate can just be rebroadcast.
Knowing what we want to get out of PSLocationManager, we can go ahead and write a delegate protocol:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
All of that should be pretty straightforward. I went ahead and included a locationManager:debugText: method.
It could be useful later for debugging things that aren’t exposed publicly.
Now the public interface for PSLocationManager can be defined:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
PSLocationManager will manage the CLLocationManager object, so it conforms to
CLLocationManagerDelegate. Most of the properties here are self-explanatory. I included a
totalSeconds property, which the delegate might use to calculate the user’s current pace.
With the exception of delegate, all the properties are readonly, since there’s no good reason
any external object should modify their values. They’ll be redefined as assign (implicitly) in the
private category, which I’ll discuss a little later on.
Note also that I’ve included a sharedLocationManager class method, so that PSLocationManager can be
used as a singleton.
The prepLocationUpdates method exists so that an app can initialize the CoreLocation service early on,
since it might take a few seconds to get an accurate GPS reading. startLocationUpdates is what will
actually begin tracking the user’s distance and speed.
And that’s all we need for the header file. I’m going to throw some defines at you for the implementation file. Don’t worry too much about them for now. They’re all used in PSLocationManager, but I won’t cover them in great detail here because they’re pretty well documented already:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
PSLocationManager keeps track of a few things that don’t need to be exposed publicly, so I used a private category in the implementation file. This separation helps to keep the role of PSLocationManager clearly defined. If you just throw all the properties in the header file, it’s really easy to just access them from other classes. And that’s a downward spiral toward bugs and spaghetti code. By keeping the header file clean of things that don’t need to be there, you’re forced to put some thought into the way you implement new functionality.
Here’s what the init method looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
self.locationHistory is used to keep track of several of the most recently received CLLocation objects.
When it’s time to calculate the user’s distance, it’ll loop through the recent locations and use the one
with the best accuracy. self.speedHistory is used to calculate the user’s current speed. CLLocation
objects (which are received from the locationManager:didUpdateToLocation:fromLocation: method) do provide
a speed property, but in this case I wanted to calculate the user’s speed myself in order to have better
control over the speed reported to the delegate. Take a look above at the comments for kPrioritizeFasterSpeeds
in the defines above to get an idea of how this will prove useful.
There are a lot of things going on in PSLocationManager, but the juiciest bits are in the
locationManager:didUpdateToLocation:fromLocation: delegate method. I’m going to pull out bits and pieces
of it here, so it’s probably easiest if you also pull up the entire method for context.
Here we go:
1 2 3 4 5 6 7 8 9 10 11 12 | |
PSLocationManager notifies its delegate of the GPS “signal” strength. Since CLLocationManager doesn’t directly
provide that info, this was my way of determining that. Basically, there are two #define constants used to
compare against the horizontalAccuracy property of a CLLocation object: kRequiredHorizontalAccuracy and
kMaximumAcceptableHorizontalAccuracy. kRequiredHorizontalAccuracy specifies the horizontal accuracy needed
for what I consider to be a strong GPS signal. kMaximumAcceptableHorizontalAccuracy specifies the maximum
horizontal accuracy that the class will accept (horizontalAccuracy is measured in meters, so the lower the
better). Thus, if the CLLocation’s horizontalAccuracy is <= kRequiredHorizontalAccuracy, the GPS signal
can be considered to be strong. Otherwise it’s weak. There’s a custom setter so that when self.signalStrength
is changed, the delegate is notified.
In order to prefer a strong GPS signal, I added the allowMaximumAcceptableAccuracy property. Its value is set
to NO when the class is initialized, and also when self.signalStrength is set to
PSLocationManagerGPSSignalStrengthStrong. If self.signalStrength is set to PSLocationManagerGPSSignalStrengthWeak,
the class waits a number of seconds (kGPSRefinementInterval), then checks self.signalStrength again. If it’s
still weak, self.allowMaximumAcceptableAccuracy is set to YES. Thus, the check in the code above. The
horizontalAccuracy defined above is used to make sure the passed-in CLLocation’s horizontalAccuracy is <=
whichever horizontal accuracy value has been chosen.
1 2 3 4 5 6 7 8 9 | |
Especially since the first few messages to locationManager:didUpdateToLocation:fromLocation: usually provide
a CLLocationManager with pretty poor horizontalAccuracy, I wanted to make sure that the class had a few
CLLocation objects to choose from before calculating the user’s distance and speed. Remember that the class
will loop through the most recent CLLocation objects to choose the one with the best accuracy.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
The above is where the best recent location is plucked out and used to increment the user’s total distance. I
also didn’t want to use CLLocation objects that are too stale, so the code uses kValidLocationHistoryDeltaInterval
to see if the location is too old to be considered.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
self.speedHistory is used to average a number (kNumSpeedHistoriesToAverage) of the previous speeds
together to find the user’s current speed. Averaging the past few speeds tends to smooth out the curve
a bit, so that reported speed changes aren’t so jarring. This was especially important in the development
of Faster, since the app changes the pace of the user’s music depending on their
speed.
Another interesting thing that was very useful for Faster, is that I added the capability to prioritize
faster speeds (kPrioritizeFasterSpeeds). What that means is that the reported speed will gradually
decrease as the user slows, due to the averaged speed history. But if the user increases their speed,
the speed reported to the delegate increases immediately. In Faster, this allows the app to give a little
leeway when a user slows down, while (nearly) instantly rewarding the user for speeding up. It makes the
app feel a bit more forgiving.
1 2 3 | |
I just want to make a quick note about this. Since locationManager:didUpdateToLocation:fromLocation:
doesn’t get sent any messages if the user isn’t moving, self.locationPingTimer was needed to determine
if the user has stopped moving. Without that timer, locationManager:didUpdateToLocation:fromLocation:
doesn’t get called again when the user stops, and thus, PSLocationManager can never report the change
in speed to its delegate.
Well, there we have it. There’s a lot more to look at in PSLocationManager. If you have questions about any of it, don’t hesitate to send me an email.
If you’re looking for some iOS, Android, or web development work to be done, consider PerspecDev for the job.