If you want to simulate GPS data in the iOS simulator you have two choices, Apple’s ‘real’ data (City run, freeway drive ect) or you can choose a single lat,long to simulate being in a static location, but… what if we want to simulate our own motorcycle ride or boat journey and work with ‘real’ GPS data in the simulator?

My open source tool ‘XJourney‘ (Available HERE) achieves just this. Using XJourney you can put your own GPS data into the iOS simulator to simulate a full GPS Journey just like Apple’s own.

So this article’s aim is to shed some light on how Apple’s GPS test data is stored in the simulator and describe the process that XJourney now automates.

In the iOS simulator we are given a few options to simulate GPS activity.

Stock ios GPS simulation options

There is ‘Custom Location‘ but this option only allows us to specify a single lat,long and I want to simulate an entire journey! So I set out to find out how Apple’s test data gets loaded into the simulator.

After a bit of digging we find Apple’s test data is stored as plists inside the iOS simulator bundle,

The location of these is:

Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/CoreLocation.framework/Support/SimulationScenarios/

Secret location of the simulation files

Popping open the ‘Freeway Drive’ plist gave me the following (I’ve removed a few hundred ‘Data’ objects)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Locations</key>
    <array>
        <data>
        YnBsaXN0MDDUAQIDBAUIKClUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJj
        aGl2ZXLRBgdUcm9vdIABowkKIVUkbnVsbNsLDA0ODxAREhMUFRYXGBkaGxwd
        Hh8gXxAma0NMTG9jYXRpb25Db2RpbmdLZXlDb29yZGluYXRlTGF0aXR1ZGVf
        ECRrQ0xMb2NhdGlvbkNvZGluZ0tleVZlcnRpY2FsQWNjdXJhY3lfEB1rQ0xM
        b2NhdGlvbkNvZGluZ0tleVRpbWVzdGFtcF8QJmtDTExvY2F0aW9uQ29kaW5n
        S2V5SG9yaXpvbnRhbEFjY3VyYWN5XxAca0NMTG9jYXRpb25Db2RpbmdLZXlM
        aWZlc3Bhbl8QJ2tDTExvY2F0aW9uQ29kaW5nS2V5Q29vcmRpbmF0ZUxvbmdp
        dHVkZV8QGmtDTExvY2F0aW9uQ29kaW5nS2V5Q291cnNlXxAca0NMTG9jYXRp
        b25Db2RpbmdLZXlBbHRpdHVkZV8QGWtDTExvY2F0aW9uQ29kaW5nS2V5U3Bl
        ZWRWJGNsYXNzXxAYa0NMTG9jYXRpb25Db2RpbmdLZXlUeXBlI0BCqupIiK/n
        I7/wAAAAAAAAI0Gy071oTZFoI0AUAAAAAAAAI0A+AAAAAAAAI8BeghVAI1ul
        I0BmR64UeuFIIwAAAAAAAAAAIz/6uFHrhR64gAIQAdIiIyQnWCRjbGFzc2Vz
        WiRjbGFzc25hbWWiJSZaQ0xMb2NhdGlvblhOU09iamVjdFpDTExvY2F0aW9u
        EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIACAARABYAHwAoADIANQA6ADwAQABG
        AF0AhgCtAM0A9gEVAT8BXAF7AZcBngG5AcIBywHUAd0B5gHvAfgCAQIKAgwC
        DgITAhwCJwIqAjUCPgJJAk4AAAAAAAACAQAAAAAAAAAqAAAAAAAAAAAAAAAA
        AAACYA==
        </data>
        <data>
        YnBsaXN0MDDUAQIDBAUIKClUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJj
        aGl2ZXLRBgdUcm9vdIABowkKIVUkbnVsbNsLDA0ODxAREhMUFRYXGBkaGxwd
        Hh8gXxAma0NMTG9jYXRpb25Db2RpbmdLZXlDb29yZGluYXRlTGF0aXR1ZGVf
        ECRrQ0xMb2NhdGlvbkNvZGluZ0tleVZlcnRpY2FsQWNjdXJhY3lfEB1rQ0xM
        b2NhdGlvbkNvZGluZ0tleVRpbWVzdGFtcF8QJmtDTExvY2F0aW9uQ29kaW5n
        S2V5SG9yaXpvbnRhbEFjY3VyYWN5XxAca0NMTG9jYXRpb25Db2RpbmdLZXlM
        aWZlc3Bhbl8QJ2tDTExvY2F0aW9uQ29kaW5nS2V5Q29vcmRpbmF0ZUxvbmdp
        dHVkZV8QGmtDTExvY2F0aW9uQ29kaW5nS2V5Q291cnNlXxAca0NMTG9jYXRp
        b25Db2RpbmdLZXlBbHRpdHVkZV8QGWtDTExvY2F0aW9uQ29kaW5nS2V5U3Bl
        ZWRWJGNsYXNzXxAYa0NMTG9jYXRpb25Db2RpbmdLZXlUeXBlI0BCqumnIxoL
        I7/wAAAAAAAAI0Gy071pTxqgI0AUAAAAAAAAI0A+AAAAAAAAI8BeghVFzOjR
        I0BmgAAAAAAAIwAAAAAAAAAAI0AAPXCj1wo9gAIQAdIiIyQnWCRjbGFzc2Vz
        WiRjbGFzc25hbWWiJSZaQ0xMb2NhdGlvblhOU09iamVjdFpDTExvY2F0aW9u
        EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIACAARABYAHwAoADIANQA6ADwAQABG
        AF0AhgCtAM0A9gEVAT8BXAF7AZcBngG5AcIBywHUAd0B5gHvAfgCAQIKAgwC
        DgITAhwCJwIqAjUCPgJJAk4AAAAAAAACAQAAAAAAAAAqAAAAAAAAAAAAAAAA
        AAACYA==
        </data>
      
          SNIP!.......<MUCH MORE DATA>.......
 
    </array>
    <key>Options</key>
    <dict>
        <key>LocationDeliveryBehavior</key>
        <real>0.0</real>
        <key>LocationRepeatBehavior</key>
        <real>2</real>
    </dict>
</dict>
</plist>

From this we can see that the plist contains a ‘Locations’ array with what looks to be Base 64 encoded entries. Many Base 64 decoders returned messy results, it eventually turned out that this is because the result is not text but a binary file (I finally had success using this decoder)

When we turn the base 64 decoded binary output back into text we can see that the base 64 encoded entries are plists.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>$archiver</key>
    <string>NSKeyedArchiver</string>
    <key>$objects</key>
    <array>
        <string>$null</string>
        <dict>
            <key>$class</key>
            <dict>
                <key>CF$UID</key>
                <integer>2</integer>
            </dict>
            <key>kCLLocationCodingKeyAltitude</key>
            <real>0.0</real>
            <key>kCLLocationCodingKeyCoordinateLatitude</key>
            <real>37.761915389999999</real>
            <key>kCLLocationCodingKeyCoordinateLongitude</key>
            <real>-122.40588588999999</real>
            <key>kCLLocationCodingKeyCourse</key>
            <real>24.609999999999999</real>
            <key>kCLLocationCodingKeyHorizontalAccuracy</key>
            <real>5</real>
            <key>kCLLocationCodingKeyLifespan</key>
            <real>30</real>
            <key>kCLLocationCodingKeySpeed</key>
            <real>26.5</real>
            <key>kCLLocationCodingKeyTimestamp</key>
            <real>315868704.34100002</real>
            <key>kCLLocationCodingKeyType</key>
            <integer>1</integer>
            <key>kCLLocationCodingKeyVerticalAccuracy</key>
            <real>-1</real>
        </dict>
        <dict>
            <key>$classes</key>
            <array>
                <string>CLLocation</string>
                <string>NSObject</string>
            </array>
            <key>$classname</key>
            <string>CLLocation</string>
        </dict>
    </array>
    <key>$top</key>
    <dict>
        <key>root</key>
        <dict>
            <key>CF$UID</key>
            <integer>1</integer>
        </dict>
    </dict>
    <key>$version</key>
    <integer>100000</integer>
</dict>
</plist>

These plists are much more interesting as they appears to be NSKeyArchived CLLocation objects each containing all the information for a single GPS location update.

Having found this out, I then manual wrote my own journey plist in the same format as Apple’s but containing my own base 64 encoded, NSArchived CLLocation objects. Once my file was placed alongside Apple’s in the folder mentioned earlier, sure enough my new entry appeared under ‘Location’ (I had to ‘Reset Content and Settings’ of the simulator before it appeared).

New simulation option

I then wrote a small desktop tool called ‘XJourney’ to automate this process, input a CSV of CLLocations and it will add these as a entry to your simulator, for more information on the XJourney tool or to download the app/or source code please see the XJourney Github page.

Tool screenshot

Happy GPS simulating!