From the beginning of Sprint-o-Mat, I always internally represented distances as an enum with an associated value:
public enum Distance { case mile(distance: Double) case km(distance: Double) static let kmPerMile = 1.60934 }
So, adding kilometer support in Sprint-o-Mat was mostly about adding in some UI.
I added a button to flip the units, but I didn’t want a straight conversion. I wanted it to round to the closest tenth.
public func flipUnitsAndRound() -> Distance { switch self { case .mile(let d): return .km(distance: round(d * Distance.kmPerMile * 10) / 10) case .km(let d): return .mile(distance: round(d / Distance.kmPerMile * 10) / 10) } }
I do the same for paces, and round them to the nearest 15 seconds.
With this done, it was mostly hunting down all of the places I was lazy—mostly inside of my HealthKit code that is getting running distance from the Watch in miles.
I left that as is, but made all of the mathematical operators for Distance
work for mixed miles and kilometers, so I could could construct a .km
Distance and then +=
.mile
distances onto it, and the conversion would be automatic, keeping the units of the left-hand side.
public func + (left: Distance, right: Distance) -> Distance { switch (left, right) { case (.mile(let ld), .mile(let rd)): return .mile(distance: ld + rd) case (.km(let ld), .km(let rd)): return .km(distance: ld + rd) case (.km(let ld), .mile(let rd)): return .km(distance: ld + rd * Distance.kmPerMile) case (.mile(let ld), .km(let rd)): return .mile(distance: ld + rd / Distance.kmPerMile) } } func += (left: inout Distance, right: Distance) { left = left + right }
If you run in kilometers, check out the new version.