11

I have been playing with MeasurementFormatter to try and display imperial lengths as 10'6" or 10 ft 6 in unsuccessfully. LengthFormatter does this correctly when isForPersonHeightUse is set to true, but it does not adapt to the user's locale well (i.e. countries where length is measured in metric, except for when referring to height). Is there any way to force this behaviour in a formatter?

EDIT This question is to determine how to choose the units for a measurement. I am able to choose feet or inches, but want to display fractional feet as inches as in: 6'3" instead of 6.25 ft.

meaning-matters
  • 21,929
  • 10
  • 82
  • 142
jjatie
  • 5,152
  • 5
  • 34
  • 56
  • Possible duplicate of [Choosing units with MeasurementFormatter](http://stackoverflow.com/questions/39858828/choosing-units-with-measurementformatter) –  Feb 15 '17 at 14:54
  • You can check if the Locale uses metric or not `Locale.current.usesMetricSystem` – Leo Dabus Feb 15 '17 at 14:55
  • @LeoDabus Thanks, I didn't know about that property. I'm still not sure how to get the right formatting for feet/inches when I detect one of those locales. You can't change the locale of a `LengthFormatter` and `MeasurementFormatter` doesn't display the length properly. – jjatie Feb 15 '17 at 14:57
  • 1
    @jjatie have you tried `lengthFormatter.numberFormatter.locale` ? – Leo Dabus Feb 15 '17 at 15:02
  • 1
    @LeoDabus That's the solution, I don't know how I missed `NumberFormatter`'s `locale` property. I'll have to check for the handful of locales that have this issue and adjust them appropriately – jjatie Feb 15 '17 at 15:11
  • Any update how you achieve your requirement? – Iqbal Khan Jun 06 '18 at 07:47
  • 1
    For anyone in the future if your output is 6 ft 3.125 in or something with a bunch of decimals. You can use `lengthFormatter.numberFormatter.maximumSignificantDigits = 2` That'll output 6 ft 3 in. – Yotzin Castrejon Apr 25 '21 at 15:52

3 Answers3

3
public struct LengthFormatters {

    public static let imperialLengthFormatter: LengthFormatter = {
        let formatter = LengthFormatter()
        formatter.isForPersonHeightUse = true
        return formatter
    }()

}

extension Measurement where UnitType : UnitLength {

    var heightOnFeetsAndInches: String? {
        guard let measurement = self as? Measurement<UnitLength> else {
            return nil
        }
        let meters = measurement.converted(to: .meters).value
        LengthFormatters.imperialLengthFormatter.string(fromMeters: meters)
    }

}

Example of using:

    let a = Measurement(value: 6.34, unit: UnitLength.feet)
    print(a.heightOnFeetsAndInches ?? "")

    let b = Measurement(value: 1.5, unit: UnitLength.feet)
    print(b.heightOnFeetsAndInches ?? "")

Will print:

6 ft, 4.08 in
1 ft, 6 in
maslovsa
  • 1,589
  • 1
  • 14
  • 15
  • I like you're answer except for having to create a `LengthFormatter` every time. That will kill scroll performance. In my use case, this isn't an issue though. – jjatie Aug 13 '18 at 10:37
  • @jjatie thanx for updates. I rewrite the code snippet – maslovsa Aug 19 '18 at 17:45
  • This conversion depends on current locale. The OP asked for a solution that's independent of the current locale. – meaning-matters Aug 01 '23 at 17:47
1

I modified (simplified) @maslovsa's answer to meet my needs. I have a Core Data object called "Patient". It has a height parameter in inches that is an Int64. I want a string that I display to the user, so here's my property on my patient object for doing so:

var heightInFeetString : String {
    let measurement = Measurement(value: Double(self.height) / 12.0, unit: UnitLength.feet)
    let meters = measurement.converted(to: .meters).value
    return LengthFormatter.imperialLengthFormatter.string(fromMeters: meters)
}

Of course, I had to implement the imperialLengthFormatter as well, but I did it as an extension to LengthFormatter itself, like this:

extension LengthFormatter {
    public static let imperialLengthFormatter: LengthFormatter = {
        let formatter = LengthFormatter()
        formatter.isForPersonHeightUse = true
        return formatter
    }()    
}

This actually doesn't kill performance as suggested in the comments for @maslova's answer. Due to the property being static, it only gets initialized once.

// When creating the Patient object
let patient = Patient(...) // Create in maanged object context
patient.height = 71

// Later displays in a collection view cell in a view controller
cell.heightLabel.Text = patient.heightInFeetString

Displays this in my table cell:

5 ft, 11 in

Matt Long
  • 24,438
  • 4
  • 73
  • 99
1

How to display Feet and Inches in SwiftUI

In case anyone arrives here looking for a SwiftUI answer.

struct MeasurementTestView: View {
    @State private var height = Measurement(value: 68, unit: UnitLength.inches)
    
    var body: some View {
        VStack {
            Text(height, format: .measurement(width: .narrow, usage: .personHeight))
            Text(height, format: .measurement(width: .abbreviated, usage: .personHeight))
            Text(height, format: .measurement(width: .wide, usage: .personHeight))
        }
        .font(.title)
    }
}

Result

enter image description here

Mark Moeykens
  • 15,915
  • 6
  • 63
  • 62