This code provide a Swift Package for parsing the Fit Files format typically produced by Garmin Devices and other fitness devices.
It uses the official Fit SDK
You can see a few example of how to use it on iOS and macOS in fit-sdk-swift
This package is also fully integrated into the open source and released MacOS and iOS apps FitFileExplorer and ConnectStats.
The open source app FitFileExplorer that uses this library can be especially helpful in exploring the structure of a fit files while working with the package.
It's fairly straight forward to use from an xcode project, following the standard approach to add a Swift Package:
- you can use the File/Swift Package/Add package dependency function, choose your project and enter the url of the package
https://github.com/roznet/FitFileParser
- Add the package as a dependency to your target
import FitFileParser
in the target code where you want to parse a fit file
A fit file is loaded using let fitfile = FitFile(file: URL)
or let fitfile = FitFile(data : Data)
You can then use access function like fitfile.messages(forMessageType: FitMessageType.session)
to get objects of type FitMessage
The type FitMessageType
represent the messages type (mesg_num in the sdk). You can access all the known messages with syntax like FitMessageType.record
, FitMessageType.session
, FitMessageType.sport
, ...
The function message.interpretedFields()
provide access to the fields for the message as a dictionary of keys to FitFieldValue
which can be either a date, a string, a double value, a double value with unit or a gps coordinate.
Here is a full example
if let fit = FitFile(file: path ) {
var gps : [CLLocationCoordinate2D] = []
var hr : [Double] = []
var ts : [Date] = []
for message in fit.messages(forMessageType: .record) {
if let one_gps = message.interpretedValue(key: "position"),
let one_hr = message.interpretedValue(key: "heart_rate"),
let one_ts = message.interpretedValue(key: "timestamp") {
if case let FitValue.coordinate(coord) = one_gps {
gps.append( coord )
}
if case let FitValue.time(date) = one_ts {
ts.append( date )
}
if case let FitValue.valueUnit(d , _) = one_hr {
hr.append( d )
}
}
}
}
Alternatively you can use the convenience optional computed property to get the value if more convenient
//...
if let one_gps = message.interpretedField(key: "position")?.coordinate,
let one_hr = message.interpretedField(key: "heart_rate")?.valueUnit?.value,
let one_ts = message.interpretedField(key: "timestamp")?.time {
gps.append( one_gps )
ts.append( one_ts)
hr.append( one_hr )
}
//...
This code builds upon the example c code from the official SDK and integrate it into swift to generate a native object with an array of messages made of native swift dictionaries. It adds support for developer fields and a .generic
parsing mode to process any message and field not predefined in the profile.
When using the .fast
parsing mode (from the sdk c example), all the keys and fields are generated from the types defined in Profile.xlsx
from the example SDK.
There are two available approaches to parsing, determined by the parsingType
argument in the FitFile
constructor:
.fast
this method will only parse the fields defined as an example in theProfile.xlsx
and therefore matching those infit_example.h
from the sdk. This approach is the fastest as it relies on pre-defined static parsing of the fields..generic
this method will blindly convert all the messages found in the files, interpreting as much as possible from the information inProfile.xlsx
as possible, but also building information from unkonwn messages and types. This approach is a bit slower as tries to interpret each fields dynamically.
All the required code is auto generated by running a python script fitsdkparser.py
that takes as input the Profile.xlsx
from the sdk.
When a new SDK is available, after download, you can copy the new Profile.xlsx
into the python
directory and edit the file fitsdkversion.txt
with the version used.
You need to then run the fitsdkparser.py
script that will automatically update the swift code for the latest version of the sdk
The main motivation to write this library was speed of parsing the fit file for the ConnectStats use case.
This library was built to replace the original cpp code from the SDK used in ConnectStats and FitFileExplorer. As ConnectStats now receives the FIT files from Garmin, the files are parsed live on the phone as they are received and performance was therefore important for the user experience.
The cpp parsing ended up very slow, and it made fit file parsing on ConnectStats or FitFileExplorer quite slow. This approach in c/swift is much faster.
You can check the benchmarking of the library versus a few others.