Here's my solution for the challenge. I tried my best to use simple concepts with as much efficiency as I could think of.
I also tried to change the values of the inputUnit and outputUnit state variable so whenever the user chooses a different conversion type, it will pick the first two units of that type. But I couldn't figure that out so I finished the challenge without doing so.
Please feel free to give any advices or suggestions. and sorry if the code hurts your eyes 😅
import SwiftUI
struct ContentView: View {
// conversion types and units arrays
let coversionTypes = ["temp", "length", "time", "volume"]
let temperatureUnits = ["celsius", "fahrenheit", "kelvin"]
let lengthUnits = ["meters", "km", "feet", "yards", "miles"]
let timeUnits = ["seconds", "minutes", "hours", "days"]
let volumeUnits = ["ml", "liters", "cups", "pints", "gallons"]
// state variables
@State private var coversionType = "temp"
@State private var inputUnit = "celsius"
@State private var outputUnit = "fahrenheit"
@State private var inputValue = 0.0
@FocusState private var inputValueIsFocused: Bool
// computed property to display units based on chosen conversion type
var conversionUnits: [String] {
switch coversionType {
case "temp":
return temperatureUnits
case "length":
return lengthUnits
case "time":
return timeUnits
default:
return volumeUnits
}
}
// computed property to convert from any input to celsius
var tempCelsius: Measurement<UnitTemperature> {
switch inputUnit {
case "fahrenheit":
return Measurement(value: inputValue, unit: UnitTemperature.fahrenheit).converted(to: .celsius)
case "kelvin":
return Measurement(value: inputValue, unit: UnitTemperature.kelvin).converted(to: .celsius)
default:
return Measurement(value: inputValue, unit: UnitTemperature.celsius)
}
}
// computed property to convert from any input to meters
var lengthMeters: Measurement<UnitLength> {
switch inputUnit {
case "km":
return Measurement(value: inputValue, unit: UnitLength.kilometers).converted(to: .meters)
case "feet":
return Measurement(value: inputValue, unit: UnitLength.feet).converted(to: .meters)
case "yards":
return Measurement(value: inputValue, unit: UnitLength.yards).converted(to: .meters)
case "miles":
return Measurement(value: inputValue, unit: UnitLength.miles).converted(to: .meters)
default:
return Measurement(value: inputValue, unit: UnitLength.meters)
}
}
// computed property to convert from any input to seconds
var timeSeconds: Measurement<UnitDuration> {
switch inputUnit {
case "minutes":
return Measurement(value: inputValue, unit: UnitDuration.minutes).converted(to: .seconds)
case "hours":
return Measurement(value: inputValue, unit: UnitDuration.hours).converted(to: .seconds)
case "days":
return Measurement(value: inputValue * 24, unit: UnitDuration.hours).converted(to: .seconds) // no day unit!
default:
return Measurement(value: inputValue, unit: UnitDuration.seconds)
}
}
// computed property to convert from any input to milliliters
var volumeMl: Measurement<UnitVolume> {
switch inputUnit {
case "liters":
return Measurement(value: inputValue, unit: UnitVolume.liters).converted(to: .milliliters)
case "cups":
return Measurement(value: inputValue, unit: UnitVolume.cups).converted(to: .milliliters)
case "pints":
return Measurement(value: inputValue, unit: UnitVolume.pints).converted(to: .milliliters)
case "gallons":
return Measurement(value: inputValue, unit: UnitVolume.gallons).converted(to: .milliliters)
default:
return Measurement(value: inputValue, unit: UnitVolume.milliliters)
}
}
// computed property to convert from any base value to the chosen output unit
var outputValue: Double {
switch outputUnit {
// temp cases (c to x)
case "celsius":
return tempCelsius.value
case "fahrenheit":
return tempCelsius.converted(to: .fahrenheit).value
case "kelvin":
return tempCelsius.converted(to: .kelvin).value
// length cases (meters to x)
case "meters":
return lengthMeters.value
case "km":
return lengthMeters.converted(to: .kilometers).value
case "feet":
return lengthMeters.converted(to: .feet).value
case "yards":
return lengthMeters.converted(to: .yards).value
case "miles":
return lengthMeters.converted(to: .miles).value
// time cases (seconds to x)
case "seconds":
return timeSeconds.value
case "minutes":
return timeSeconds.converted(to: .minutes).value
case "hours":
return timeSeconds.converted(to: .hours).value
case "days":
return timeSeconds.converted(to: .hours).value / 24 // no day unit!
// volume cases (ml to x)
case "ml":
return volumeMl.value
case "liters":
return volumeMl.converted(to: .liters).value
case "cups":
return volumeMl.converted(to: .cups).value
case "pints":
return volumeMl.converted(to: .pints).value
case "gallons":
return volumeMl.converted(to: .gallons).value
// default (return input value)
default:
return inputValue
}
}
var body: some View {
NavigationView {
Form {
// conversion picker
Section {
Picker("Coversion Type", selection: $coversionType) {
ForEach(coversionTypes, id: \.self) {
Text($0)
}
}.pickerStyle(.segmented)
} header: {
Text("Choose conversion type")
}
// input
Section {
Picker("Input Unit", selection: $inputUnit) {
ForEach(conversionUnits, id: \.self) {
Text($0)
}
}.pickerStyle(.segmented)
TextField("Input Value", value: $inputValue, format: .number)
.keyboardType(.decimalPad)
.focused($inputValueIsFocused)
} header: {
Text("Choose input unit & enter input value")
}
// output
Section {
Picker("Output Unit", selection: $outputUnit) {
ForEach(conversionUnits, id: \.self) {
Text($0)
}
}.pickerStyle(.segmented)
Text(outputValue.formatted())
} header: {
Text("Choose output unit")
}
// nav title, toolbar to hide keyboard
}
.navigationTitle("Units Converter")
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") {
inputValueIsFocused = false
}
}
}
}
}
}