Recently, as a part of a proof of concept for certain personalized interface around user’s location, we realized that the best way to showcase the feature would be to simulate a users journey. on an iPhone. without any cables connecting to a Xcode instance. So, we set out to figure out what it takes to simulate a user location and user’s journey from point A to point B, on an actual device on demand, without using Xcode to simulate location.
The logical step would be to first setup the route to simulate. The easiest way would be to get directions on google map and then convert it to a .gpx file here. If you are simulating navigation route, make sure you select track points options.
The gpx file created looks something like this:
a sample gpx file showing trackpoints!
save this gpx file to your XCode project for later use.
Next step is to be able to parse this gpx file as a collection of CLLocation structures which can be then simulated. The following gist shows how it can be done.
The class which is responsible for invoking GpxParser, also conforms to the GpxParsing protocol which allows it to proceed further once the parsing completes. The output of this parsing is a Queue<CLLocation>. Implementing a Queue should be trivial (or a good exercise to do :) ).
CLLocationManager (from CoreLocation module) is THE primary class which provides all the location specific functionality to an iOS application. If you are writing a location aware application, you would implement a CLLocationManager. If you are using MKMapView to show maps, MKMapView would internally rely on CLLocationManager to provide location details. So, It makes sense for us to create a MockCLLocationManager and mimic CLLocationManager using the parsed gpx file locations and serve them at intervals.
MockCLLocationManager provides some sugar methods like startMocks using a gpx file’s name, or stop it.
As a recap, we now have capability to express a route as a queue of CLLocation structures. We also have capability to mock CLLocationManager and mimic change of locations. All we need to do now is redirect all the requests to CLLocationManager to this MockCLLocationManager.
Sounds simple. But How?
One word: Swizzling!
NSHipster has an excellent blog explaining swizzling. Simply put, it is a way to change behavior of a class (specifically it’s methods) at runtime. Idea is that whenever runtime tries to access CLLocationManager, our method which we have “swizzled” executes same on MockCLLocationManager and calls appropriate delegates so that the caller gets the requested location.
The swizzling is completed in DLAppDelegate by calling CLLocationManager.classInit (this is the place you would check for debug or production build).
Enough of the words. here’s the gist:
One may argue that why Swizzle and not just use MockCLLocationManager instance?. There are two issues with that approach. It won’t help in simulating location updates on MKMapView as it uses internal instance of CLLocationManager ( thank you Apple! :| ). Secondly, we have just one location to toggle this behavior. Honestly, you wouldn’t want to even accidentally ship this code to production!
word.
below’s the view controller gist. You would see there are no traces of any mocking.
And here’s code in action!