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.