skip to Main Content

I’m hoping someone can help me with this issue that’s driving me nuts.

I want to use the formatted() modifier with Measurements so I can get locale specific units, and it’s working great for mass and temperature.

I correctly get oz/g and F/C but for volume I’m getting cm3/in3 instead of ml/floz.
I realize that is a Volumetric unit, but It’s not the one i want.

Text("(Measurement(value: Double(volume), unit: UnitVolume.milliliters).formatted())")
Text("(Measurement(value: Double(mass), unit: UnitMass.grams).formatted())")
Text("(Measurement(value: Double(temp), unit: UnitTemperature.celsius).formatted())")

I know I can do this

Text("(Measurement(value: Double(brew.water), unit: UnitVolume.milliliters).formatted(.measurement(width: .abbreviated, usage: .asProvided, numberFormatStyle: .number)))")

But then I have to figure out the locale myself.
Is there a way to this with the builtins in swiftui?

Thanks in advance

2

Answers


  1. Your last attempt is close. Just change the usage to .liquid instead of asProvided.

    Text("(Measurement(value: Double(brew.water), unit: UnitVolume.milliliters).formatted(.measurement(width: .abbreviated, usage: .liquid, numberFormatStyle: .number)))")
    

    This gives me the result as "fl oz" with a locale of en_US.

    Note that MeasurementFormatUnitUsage<UnitVolume>.liquid is only available as of iOS 16.0 so this solution won’t work with iOS 15.

    Login or Signup to reply.
  2. You can use converted and MeasurementFormatter

    In its simplest form it would look something like

    struct VolumeSampleView: View {
        let volume = Measurement(value: 10, unit: UnitVolume.liters)
        var body: some View {
            Text(volume.converted(to: .milliliters), formatter: MeasurementFormatter())
        }
    }
    

    But in an extension you can add customizations that can be reused and it works similar to formatted but allows for iOS 14+.

    extension Formatter{
        static func measurement(unitOptions: MeasurementFormatter.UnitOptions = .providedUnit, unitStyle: Formatter.UnitStyle = .short, numberStyle: NumberFormatter.Style = .decimal) -> MeasurementFormatter{
            
            let f = MeasurementFormatter()
            f.unitOptions = unitOptions
            f.unitStyle = unitStyle
            
            let nf = NumberFormatter()
            nf.numberStyle = numberStyle
            
            f.numberFormatter = nf
            
            return f
        }
    }
    

    And then your View would evolve to look something like

    struct VolumeSampleView: View {
        let volume = Measurement(value: 10, unit: UnitVolume.liters)
        let mass = Measurement(value: 10, unit: UnitMass.kilograms)
        var body: some View {
            Text(volume.converted(to: .milliliters), formatter: .measurement())
            Text(mass.converted(to: .grams), formatter: .measurement())
    
        }
    }
    

    You can also extend Measurement itself and add a method that provides the string automatically.

    extension Measurement{
        func toString(unitOptions: MeasurementFormatter.UnitOptions = .providedUnit, unitStyle: Formatter.UnitStyle = .short, numberStyle: NumberFormatter.Style = .decimal) -> String{
            
            let f = Formatter.measurement(unitOptions: unitOptions, unitStyle: unitStyle, numberStyle: numberStyle)
            return f.string(from: self)
        }
    }
    

    Then you don’t have to use formatter you can just call toString() which would allow for an answer that works with all SwiftUI versions.

    struct VolumeSampleView: View {
        let volume = Measurement(value: 10, unit: UnitVolume.liters)
        let mass = Measurement(value: 10, unit: UnitMass.kilograms)
        let temp = Measurement(value: 30, unit: UnitTemperature.fahrenheit)
        var body: some View {
            VStack{
                Text(volume.converted(to: .milliliters).toString())
                Text(volume.converted(to: .fluidOunces).toString())
                Text(mass.converted(to: .grams).toString())
                Text(temp.converted(to: .celsius).toString())
            }
        }
    }
    

    enter image description here

    To show locale adjusted measurements you can just add .current to the Locale

    extension Formatter{
        static func measurement(unitOptions: MeasurementFormatter.UnitOptions? = .providedUnit, unitStyle: Formatter.UnitStyle = .short, numberStyle: NumberFormatter.Style = .decimal) -> MeasurementFormatter{
            
            let f = MeasurementFormatter()
            f.unitStyle = unitStyle
            
            if let options = unitOptions{ //Make unitOptions optional
                f.unitOptions = options
            }
            
            f.locale = .current //Add locale consideration
    
            let nf = NumberFormatter()
            nf.numberStyle = numberStyle
            
            f.numberFormatter = nf
            
            return f
        }
    }
    extension Measurement{
        func toString(unitOptions: MeasurementFormatter.UnitOptions? = .providedUnit, unitStyle: Formatter.UnitStyle = .short, numberStyle: NumberFormatter.Style = .decimal) -> String{
            
            let f = Formatter.measurement(unitOptions: unitOptions, unitStyle: unitStyle, numberStyle: numberStyle)
            return f.string(from: self)
        }
    }
    

    Then just set the unitOptions argument to nil when you want the units to show adjusted by locale

    Text(volume.toString(unitOptions: nil)) //Will show locale specific version
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search