Created
February 19, 2020 18:33
-
-
Save chriseidhof/8f043206036704d13ecc9f3736e5449e to your computer and use it in GitHub Desktop.
Race Pace Calculator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ContentView.swift | |
// RunningPaceCalculator | |
// | |
// Created by Chris Eidhof on 19.02.20. | |
// Copyright © 2020 Chris Eidhof. All rights reserved. | |
// | |
import SwiftUI | |
struct Pace: Codable, Equatable, Hashable { | |
var secondsPerKM: Double | |
var secondsPerMile: Double { | |
return secondsPerKM * 1.60934 | |
} | |
} | |
struct Meters: Codable, Equatable, Hashable { | |
var meters: Double | |
init(floatLiteral: Double) { self.meters = floatLiteral } | |
} | |
extension Meters: ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral { | |
init(integerLiteral value: Double) { | |
meters = value | |
} | |
} | |
enum Distance { | |
case marathon | |
case halfMarathon | |
case other(Meters) | |
var displayName: String { | |
switch self { | |
case .marathon: return "Marathon" | |
case .halfMarathon: return "Half Marathon" | |
case .other(let m): return "\(Int(m.meters))m" | |
} | |
} | |
var distance: Meters { | |
switch self { | |
case .marathon: return 42195 | |
case .halfMarathon: return 21097.5 | |
case .other(let m): return m | |
} | |
} | |
static let builtin: [Distance] = [ | |
.marathon, | |
.halfMarathon, | |
.other(10000), | |
.other(5000), | |
.other(1500), | |
.other(800), | |
.other(400), | |
.other(200) | |
] | |
} | |
struct Time: Codable, Equatable, Hashable { | |
var seconds: Double | |
} | |
let timeFormatter: DateComponentsFormatter = { | |
var t = DateComponentsFormatter() | |
t.allowedUnits = [.hour, .minute, .second] | |
t.unitsStyle = .positional | |
return t | |
}() | |
extension Time { | |
var formatted: String { | |
return timeFormatter.string(from: TimeInterval(seconds))! | |
} | |
} | |
func *(lhs: Pace, rhs: Meters) -> Time { | |
return Time(seconds: lhs.secondsPerKM * rhs.meters/1000) | |
} | |
func *(lhs: Pace, rhs: Double) -> Pace { | |
return Pace(secondsPerKM: lhs.secondsPerKM * rhs) | |
} | |
extension Pace { | |
var minutesPerKM: String { | |
let formatted = timeFormatter.string(from: TimeInterval(secondsPerKM))! | |
return "\(formatted)/km" | |
} | |
var minutesPerMile: String { | |
let formatted = timeFormatter.string(from: TimeInterval(secondsPerMile))! | |
return "\(formatted)/mi" | |
} | |
} | |
struct TimeSlider: View { | |
@Binding var pace: Pace | |
var body: some View { | |
HStack(alignment: .bottom, spacing: 5) { | |
ForEach(0..<(10*60)) { tick in | |
Rectangle() | |
.fill(Color.black) | |
.frame(width: 2, height: (tick % 10 == 0) ? 30 : 15) | |
} | |
} | |
} | |
} | |
struct ContentView: View { | |
@State var pace: Pace = Pace(secondsPerKM: 180+46) | |
var body: some View { | |
let separator = Rectangle() | |
.frame(height: 1) | |
.padding() | |
return VStack { | |
HStack { | |
Button("-") { self.pace.secondsPerKM -= 1 } | |
Text(pace.minutesPerKM) | |
Button("+") { self.pace.secondsPerKM += 1 } | |
}.font(Font.largeTitle.monospacedDigit()) | |
Text(pace.minutesPerMile).font(Font.body.monospacedDigit()) | |
Text("") | |
separator | |
ForEach(Array(Distance.builtin.enumerated()), id: \.offset) { (offset, el) in | |
HStack { | |
Text(el.displayName).frame(maxWidth: .infinity, alignment: .trailing) | |
Text((self.pace * el.distance).formatted).frame(maxWidth: .infinity, alignment: .leading) | |
} | |
}.font(Font.body.monospacedDigit()) | |
separator | |
ForEach([10, 15, 20], id: \.self) { perc in | |
HStack { | |
Text("\(perc)% slower") | |
.frame(maxWidth: .infinity, alignment: .trailing) | |
Text((self.pace * (1 + Double(perc)/100)).minutesPerKM) | |
.frame(maxWidth: .infinity, alignment: .leading) | |
} | |
}.font(Font.body.monospacedDigit()) | |
} | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment