How to properly share / export GPX files on iOS
In upcoming version 4.1 of my Run 5k app, I’m adding support for exporting data out of the app. I strongly believe that data liberation should exist in all applications and services despite the fact it can require significant amount of time to do properly. In 4.1, you will be able to export complete GPX file for each run you did with Run 5k.
What is GPX
GPX is de-facto standard for exchanging GPS tracks, waypoints, and similar GPS related data between applications and services. It stands for GPS Exchange Format and was created by Topografix. Current version of the specification is 1.1.
Over time, companies like Garmin have added Extensions to the original specification – thus you can include things like heart rate to it. Which all later lead them to create an expanded format called TCX which allows you to transfer many fitness-related bits of information.
How to do it on iOS
The hardest part of the work is already done by Watanabe Toshinori, who created and open-soured GPX framework for creating and parsing GPX formats, plus also a separate Logger and Viewer. If you look at the GitHub profile, you’ll find the same for Google’s KML as well. Truly an amazing contribution to the iOS community and people like Watanabe inspire me to share my knowledge and code.
Sadly, the last Watanabe’s update was 3 years ago and many other developers have taken the mantle and continued to improve the library. Simply pick the one you want, even the original still works just fine on iOS 8.
Once you include the framework in your own app, it’s pretty straightforward to instantiate the whole hierarchy and convert your CLLocation
stuff into GPX
objects.
How to add GPX support to iOS, system-wide
With that out of the way, the troubling part is how to actually share the file on iOS. Why troubling? All file exchanging in iOS is dependent on the OS knowing about the file format in the first place. This is done through Uniform Type Identifier system, or UTI. Read Apple docs or this old Cocoanetics blog post to learn more.
For now, it’s enough to know that in order to attach a particular file to — say Mail message, iOS must have its UTI record. Ideally, you would already have another app that acts as GPX viewer and or editor. Thus the UTI declaration for GPX would already be available to iOS runtime.
By default, iOS installation does not have GPX record and thus you can’t depend on it being there. Hence to successfully export GPX file, you need to add Imported UTI Declaration to your app’s Info.plist file; “imported” means that you are declaring someone else’s file format, in this case Topografix’s GPX. (If your app defines your own custom file format, you would use Exported UTI Declaration.)
This is what you need to add to the end of the Info.plist file:
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>com.topografix.gpx</string>
<key>UTTypeReferenceURL</key>
<string>http://www.topografix.com/GPX/1/1</string>
<key>UTTypeDescription</key>
<string>GPS Exchange Format (GPX)</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.xml</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>gpx</string>
</array>
<key>public.mime-type</key>
<string>application/gpx+xml</string>
</dict>
</dict>
</array>
This above is the minimum required. I got to this through trial and error, adding one key at the time to see what’s enough. Note that some apps are stricter than others and I found that out of all the system apps Mail is the most strict. For example, you could share – using standard share system in iOS 8 – .gpx file without the public.mime-type
key present through iMessage but Mail would simply ignore it. Add that one and sharing to Mail will work (your file will appear as attachment).
Last piece of the puzzle: how to share the file in iOS 8
I like the sharing mechanism that Apple is building with iOS 8 through UIActivity*
APIs and I’m adding support for it in all my apps, as appropriate for each one.
GPX file is perfect candidate for lazy-creation share mechanism, since it can take a while to be created. If you do it before the share sheet is even shown, you could potentially stall your UI for seconds.
Thus you need to subclass UIActivityItemProvider
and implement just these two methods as minimum:
- (id)item {
NSString *filePath = [self.activity saveToFileGPXString:[self.activity getGPXString]];
if ([filePath length] == 0) return nil;
NSError *error = nil;
NSData *fileData = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMapped error:&error];
if (error) {
NSLog(@"Error reading GPX file:\n%@", error);
}
return fileData;
}
- (NSString *)activityViewController:(UIActivityViewController *)activityViewController dataTypeIdentifierForActivityType:(NSString *)activityType {
return @"com.topografix.gpx";
}
First method generates GPX file and saves it to some folder and then streams it using dataWithContentsOfFile:options:error
. (You can also send NSData
from the memory, bypassing the file system.)
Second method is key: you must tell the sharing mechanism what kind of file is it, using UTI you declared in your Info.plist
file. If you don’t, Mail will again ignore the file (while Messages will still attach it).
The only part I could not figure out is how to name the file - both Mail and Messages ignore the file name and display Attachment-1.gpx
. (blah)
So, there you have it. Nice thing to do is to also include (NSString *)activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType
and return nice, informative subject, which Mail app will read out and save your customers some typing.