How can I stop the app from crashing when I switch picker values?

Viewed 68

I am making a unit converter app. It works well except for one issue. When I change the value of the second and third pickers and then change the value of the first picker, the app crashes. I suspect that this is because it is being set to an index out of range. Is there any way to fix this? Is it possible to set the values of fromUnitsIndex and toUnitsIndex back to 0 when the value of unitTypesIndex changes?

Here is the code:

func convert(_ value: Double, from sourceUnit: Dimension, to targetUnit: Dimension) -> Double {
    let a = Measurement(value: value, unit: sourceUnit)
    let b = a.converted(to: targetUnit).value
    return b
}

struct ContentView: View {
    @State private var fromUnitsIndex:Int = 0
    @State private var toUnitsIndex:Int = 0
    @State private var unitTypesIndex:Int = 0
    private var unitTypesArr = ["Acceleration", "Angle", "Area", "Concentration of Mass", "Duration", "Electric Charge", "Electric Current", "Electric Potential Difference", "Electrical Resistance", "Energy", "Frequency", "Fuel Efficiency", "Length", "Mass", "Power", "Pressure", "Speed", "Temperature", "Volume"]
    private var unitsArr = [["Meters Per Second Squared", "Gravity"], ["Degrees", "Radians", "Arc Minutes", "Arc Seconds", "Gradians", "Revolutions"], ["Square Nanometers", "Square Micrometers", "Square Millimeters", "Square Centimeters", "Square Meters", "Square Kilometers", "Square Megameters", "Square Inches", "Square Feet", "Square Yards", "Square Miles", "Acres", "Hectares", "Ares"], ["Grams Per Liter", "Milligrams Per Deciliter", "Millimoles Per Liter"], ["Seconds", "Minutes", "Hours"], ["Coulombs", "Microampere Hours", "Milliampere Hours", "Ampere Hours", "Kiloampere Hours", "Megaampere Hours"], ["Microamperes", "Milliamperes", "Amperes", "Kiloamperes", "Megaamperes"], ["Microvolts", "Millivolts", "Volts", "Kilovolts", "Megavolts"], ["Microohms", "Milliohms", "Ohms", "Kiloohms", "Megaohms"], ["Joules", "Kilojoules", "Calories", "Kilocalories", "Kilowatt Hours"], ["Nanohertz", "Microhertz", "Millihertz", "Hertz", "Kilohertz", "Megahertz", "Gigahertz", "Terahertz"], ["Liters Per 100 km", "Miles Per Gallon", "Miles Per Imperial Gallon"], ["Picometers", "Nanometers", "Micrometers", "Millimeters", "Centimeters", "Decimeters", "Meters", "Decameters", "Hectometers", "Kilometers", "Megameters", "Inches", "Feet", "Yards", "Miles", "Nautical Miles", "Light Years", "Astronomical Units", "Parsecs", "Fathoms", "Furlongs", "Scandinavian Miles"], ["Picograms", "Nanograms", "Micrograms", "Milligrams", "Centigrams", "Decigrams", "Grams", "Kilograms", "Metric Tons", "Ounces", "Pounds", "Stones", "Short Tons", "Carats", "Ounces Troy", "Slugs"], ["Femtowatts", "Picowatts", "Nanowatts", "Microwatts", "Milliwatts", "Watts", "Kilowatts", "Megawatts", "Gigawatts", "Terawatts", "Horsepower"], ["Pascals", "Hectopascals", "Kilopascals", "Megapascals", "Gigapascals", "Millibars", "Bars", "Pounds Per Square Inch", "Millimeters of Mercury", "Inches of Mercury"], ["Meters Per Second", "Kilometers Per Hour", "Miles Per Hour", "Knots"], ["Degrees Celsius", "Degrees Fahrenheit", "Degrees Kelvin"], ["Milliliters", "Centiliters", "Deciliters", "Liters", "Kiloliters", "Megaliters", "Cubic Millimeters", "Cubic Decimeters", "Cubic Meters", "Cubic Kilometers", "Fluid Ounces", "Cups", "Pints", "Quarts", "Gallons", "Cubic Inches", "Cubic Feet", "Cubic Yards", "Cubic Miles", "Acre Feet", "Bushels", "Teaspoons", "Tablespoons", "Imperial Teaspoons", "Imperial Tablespoons", "Imperial Fluid Ounces", "Imperial Pints", "Imperial Quarts", "Imperial Gallons", "Metric Cups"]]
    private var convUnitsArr = [[UnitAcceleration.metersPerSecondSquared, UnitAcceleration.gravity], [UnitAngle.degrees, UnitAngle.radians, UnitAngle.arcMinutes, UnitAngle.arcSeconds, UnitAngle.gradians, UnitAngle.revolutions], [UnitArea.squareNanometers, UnitArea.squareMicrometers, UnitArea.squareMillimeters, UnitArea.squareCentimeters, UnitArea.squareMeters, UnitArea.squareKilometers, UnitArea.squareMegameters, UnitArea.squareInches, UnitArea.squareFeet, UnitArea.squareYards, UnitArea.squareMiles, UnitArea.acres, UnitArea.hectares, UnitArea.ares], [UnitConcentrationMass.gramsPerLiter, UnitConcentrationMass.milligramsPerDeciliter, UnitConcentrationMass.millimolesPerLiter], [UnitDuration.seconds, UnitDuration.minutes, UnitDuration.hours], [UnitElectricCharge.coulombs, UnitElectricCharge.microampereHours, UnitElectricCharge.milliampereHours, UnitElectricCharge.ampereHours, UnitElectricCharge.kiloampereHours, UnitElectricCharge.megaampereHours], [UnitElectricCurrent.microamperes, UnitElectricCurrent.milliamperes, UnitElectricCurrent.amperes, UnitElectricCurrent.kiloamperes, UnitElectricCurrent.megaamperes], [UnitElectricPotentialDifference.microvolts, UnitElectricPotentialDifference.millivolts, UnitElectricPotentialDifference.volts, UnitElectricPotentialDifference.kilovolts, UnitElectricPotentialDifference.megavolts], [UnitElectricResistance.microohms, UnitElectricResistance.milliohms, UnitElectricResistance.ohms, UnitElectricResistance.kiloohms, UnitElectricResistance.megaohms], [UnitEnergy.joules, UnitEnergy.kilojoules, UnitEnergy.calories, UnitEnergy.kilocalories, UnitEnergy.kilowattHours], [UnitFrequency.nanohertz, UnitFrequency.microhertz, UnitFrequency.millihertz, UnitFrequency.hertz, UnitFrequency.kilohertz, UnitFrequency.megahertz, UnitFrequency.gigahertz, UnitFrequency.terahertz], [UnitFuelEfficiency.litersPer100Kilometers, UnitFuelEfficiency.milesPerGallon, UnitFuelEfficiency.milesPerImperialGallon], [UnitLength.picometers, UnitLength.nanometers, UnitLength.micrometers, UnitLength.millimeters, UnitLength.centimeters, UnitLength.decimeters, UnitLength.meters, UnitLength.decameters, UnitLength.hectometers, UnitLength.kilometers, UnitLength.megameters, UnitLength.inches, UnitLength.feet, UnitLength.yards, UnitLength.miles, UnitLength.nauticalMiles, UnitLength.lightyears, UnitLength.astronomicalUnits, UnitLength.fathoms, UnitLength.furlongs, UnitLength.scandinavianMiles], [UnitMass.picograms, UnitMass.nanograms, UnitMass.micrograms, UnitMass.milligrams, UnitMass.centigrams, UnitMass.decigrams, UnitMass.grams, UnitMass.kilograms, UnitMass.metricTons, UnitMass.ounces, UnitMass.pounds, UnitMass.stones, UnitMass.shortTons, UnitMass.carats, UnitMass.ouncesTroy, UnitMass.slugs], [UnitPower.femtowatts, UnitPower.picowatts, UnitPower.nanowatts, UnitPower.microwatts, UnitPower.milliwatts, UnitPower.watts, UnitPower.kilowatts, UnitPower.megawatts, UnitPower.gigawatts, UnitPower.terawatts, UnitPower.horsepower], [UnitPressure.newtonsPerMetersSquared, UnitPressure.hectopascals, UnitPressure.kilopascals, UnitPressure.megapascals, UnitPressure.gigapascals, UnitPressure.millibars, UnitPressure.bars, UnitPressure.poundsForcePerSquareInch, UnitPressure.millimetersOfMercury, UnitPressure.inchesOfMercury], [UnitSpeed.metersPerSecond, UnitSpeed.kilometersPerHour, UnitSpeed.milesPerHour, UnitSpeed.knots], [UnitTemperature.celsius, UnitTemperature.fahrenheit, UnitTemperature.kelvin], [UnitVolume.milliliters, UnitVolume.centiliters, UnitVolume.deciliters, UnitVolume.liters, UnitVolume.kiloliters, UnitVolume.megaliters, UnitVolume.cubicMillimeters, UnitVolume.cubicDecimeters, UnitVolume.cubicMeters, UnitVolume.cubicKilometers, UnitVolume.fluidOunces, UnitVolume.cups, UnitVolume.pints, UnitVolume.quarts, UnitVolume.gallons, UnitVolume.cubicInches, UnitVolume.cubicFeet, UnitVolume.cubicYards, UnitVolume.cubicMiles, UnitVolume.acreFeet, UnitVolume.bushels, UnitVolume.teaspoons, UnitVolume.tablespoons, UnitVolume.imperialTeaspoons, UnitVolume.imperialTablespoons, UnitVolume.imperialFluidOunces, UnitVolume.imperialPints, UnitVolume.imperialQuarts, UnitVolume.imperialGallons, UnitVolume.metricCups]]
    @State private var inputValue:String = ""
    var body: some View {
        GeometryReader { geometry in
            VStack {
                Text("Unit Converter").font(.largeTitle).fontWeight(.semibold)
                Spacer()
                HStack(spacing: 0){
                    VStack {
                        Spacer()
                        TextField("Enter a value", text: self.$inputValue)
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                            .keyboardType(.decimalPad)
                            .frame(width: geometry.size.width/2.5)
                        Text("\(self.unitsArr[self.unitTypesIndex][self.fromUnitsIndex])")
                            .lineLimit(1)
                            .minimumScaleFactor(0.5)
                            .foregroundColor(.white)
                    }
                    .frame(minWidth: 0, maxWidth: .infinity)
                    Spacer()
                    Text("=")
                        .font(.title)
                        .foregroundColor(.white)
                    Spacer()
                    VStack (spacing: 10){
                        Spacer()
                        Text("\(convert((self.inputValue as NSString).doubleValue, from: UnitAcceleration.metersPerSecondSquared, to: UnitAcceleration.metersPerSecondSquared))")
                            .frame(width: geometry.size.width/2.5)
                            .foregroundColor(.white)
                        Text("\(self.unitsArr[self.unitTypesIndex][self.toUnitsIndex])")
                        .lineLimit(1)
                        .minimumScaleFactor(0.5)
                        .foregroundColor(.white)
                    }
                    .frame(minWidth: 0, maxWidth: .infinity)
                }
                .frame(maxWidth: geometry.size.width/1.15, maxHeight: geometry.size.height/9)
                .padding()
                .background(Color.blue)
                .cornerRadius(25.0)
                Spacer()
                VStack {
                    Text("Unit Type")
                        .fontWeight(.medium)
                        .foregroundColor(.white)
                        .font(.title)
                    Picker(selection: self.$unitTypesIndex, label: Text("Unit Type").foregroundColor(.white)) {
                        ForEach(0..<self.unitTypesArr.count) { index in
                            Text(self.unitTypesArr[index])
                                .foregroundColor(.white)
                                .fontWeight(.semibold)
                                .tag(index)
                        }
                    }
                    .frame(maxHeight: geometry.size.height/9)
                    .clipped()
                    .labelsHidden()
                    .pickerStyle(DefaultPickerStyle())
                }
                .padding()
                .background(Color.blue)
                .cornerRadius(25.0)
                VStack {
                    Text("Convert From")
                        .fontWeight(.medium)
                        .foregroundColor(.white)
                        .font(.title)
                    Picker(selection: self.$fromUnitsIndex, label: Text("Unit Type").foregroundColor(.white)) {
                        ForEach(0..<self.unitsArr[self.unitTypesIndex].count) { index in
                            Text(self.unitsArr[self.unitTypesIndex][index])
                                .foregroundColor(.white)
                                .fontWeight(.semibold)
                                .tag(index)
                        }
                    }
                    .id(self.unitTypesIndex*10+self.fromUnitsIndex)
                    .frame(maxHeight: geometry.size.height/9)
                    .clipped()
                    .labelsHidden()
                    .pickerStyle(DefaultPickerStyle())
                }
                .padding()
                .background(Color.blue)
                .cornerRadius(25.0)
                VStack {
                    Text("Convert To")
                        .fontWeight(.medium)
                        .foregroundColor(.white)
                        .font(.title)
                    Picker(selection: self.$toUnitsIndex, label: Text("Unit Type").foregroundColor(.white)) {
                        ForEach(0..<self.unitsArr[self.unitTypesIndex].count) { index in
                            Text(self.unitsArr[self.unitTypesIndex][index])
                                .foregroundColor(.white)
                                .fontWeight(.semibold)
                                .tag(index)
                        }
                    }
                    .id(self.unitTypesIndex*10+self.toUnitsIndex)
                    .frame(maxHeight: geometry.size.height/9)
                    .clipped()
                    .labelsHidden()
                    .pickerStyle(DefaultPickerStyle())
                }
                .padding()
                .background(Color.blue)
                .cornerRadius(25.0)
                Spacer()
            }
            .padding()
        }
    }
}
1 Answers

You could create an observable object,

class UnitViewModel: ObservableObject {

    @Published var fromUnitsIndex: Int = 0
    @Published var toUnitsIndex: Int = 0

    init() {
        fromUnitsIndex = 0
        toUnitsIndex = 0
    }

    @Published var unitTypeIndex: Int = 0 {
        didSet {
            fromUnitsIndex = 0
            toUnitsIndex = 0
        }
    }
}

didSet will set your two variables back to 0 when unitTypeIndex changes. Then you can use the observable object instead of state variables by writing this line at the top of your view.

@ObservedObject var viewModel: UnitViewModel

Then instead of using self. just use viewModel.

Related