简体   繁体   中英

Determine user's "Temperature Unit" setting on iOS 10 (Celsius / Fahrenheit)

iOS 10 adds the ability for the user to set their "Temperature Unit" choice under Settings > General > Language & Region > Temperature Unit.

How can my app programmatically determine this setting so it can display the right temperature unit? I poured through NSLocale.h and didn't see anything relevant.

(Before iOS 10, it was sufficient to test if the locale used metric and assume that metric users want to use Celsius. This is no longer the case.)

There is an (NS)MeasurementFormatter class. It inherits from an (NS)Formatter class. It's a new class available for iOS 10+ SDK.

I am not sure whether it's necessary to know, what unit a user has set in their preferences.

To set a Measurement using Swift 3 :

let formatter = MeasurementFormatter()
let measurement = Measurement(value: 24.5, unit: UnitTemperature.celsius)
let temperature = formatter.string(from: measurement)
print(temperature) // 76.1°F 
// this value was computed to Fahrenheit value on my locale/preferences

For retrieval of a Measurement :

print(measurement.unit) // °C - always celsius as it was set as Celsius

formatter.unitStyle = .long

formatter.locale = Locale.current
formatter.string(from: measurement.unit) // degrees Celsius - always an original unit
formatter.string(from: measurement) // 76.1 degrees Fahrenheit - regarding locale/settings

formatter.locale = Locale.init(identifier: "it_IT")
formatter.string(from: measurement.unit) // gradi Celsius - always an original unit
formatter.string(from: measurement) // 24,5 gradi Celsius - regarding locale/settings

The system knows, what unit we have set. It will handle all the value conversion work, because the value was set as a pair of a value and a measurement unit .

For manual conversion:

measurement.converted(to: UnitTemperature.kelvin).value  // 297.65

Swift:

https://developer.apple.com/reference/foundation/measurementformatter

Objective-C:

https://developer.apple.com/reference/foundation/nsmeasurementformatter?language=objc


Feel free to correct a grammar.

There is this article by Alexandre Colucci that I found: http://blog.timac.org/?tag=nslocaletemperatureunit

First, expose the NSLocaleTemperatureUnit NSLocaleKey:

FOUNDATION_EXPORT NSLocaleKey const NSLocaleTemperatureUnit;

Then check the unit with this:

temperatureUnit = [[NSLocale currentLocale] objectForKey:NSLocaleTemperatureUnit]

Or Swift (2.3):

if let temperatureUnit = NSLocale.currentLocale().objectForKey(NSLocaleTemperatureUnit) {
    ...
}

It will return a string which is either "Celcius" or "Fahrenheit".

But there is an issue: it's not backwards compatible with iOS versions earlier than 10. If you run your app on an iOS 9 or earlier device, you'll get an error "dyld: Symbol not found: _NSLocaleTemperatureUnit" during app startup.

The solution is to use weak linking for the NSLocaleTemperatureUnit variable definition. Like this:

FOUNDATION_EXPORT NSLocaleKey const NSLocaleTemperatureUnit  __attribute__((weak_import));

This will let the app pass the dyld checks. But now you will have to check for the OS version before using NSLocaleTemperatureUnit, or your app will crash with an exception.

if #available(iOS 10,*) {
    if let temperatureUnit = NSLocale.currentLocale().objectForKey(NSLocaleTemperatureUnit) {
        ...
    }
}

EDIT:

I tried it in my app, but Apple rejected the app for it when I uploaded it to Testflight. So they definitely don't want us to use the setting for our own formatter classes. I find that pretty annoying.

The correct answer is from Fabio's comment to pedrouan's answer , which references Apple's response on the forums: https://forums.developer.apple.com/thread/70258 .

The Simulator does not respect the Temperature Unit setting, but real devices do respect that setting. Using MeasurementFormatter in the Simulator will always use the locale's default unit, but using MeasurementFormatter on a real device will use the user's selected Temperature Unit.

This seems to be a late answer, but I found a neat solution mentioned by @fábio-oliveira in the comments here.

The original Apple thread: https://forums.developer.apple.com/thread/70258

What I'm trying to do in my solution is to have a function to convert Kelvin temperature (parsed from JSON file and can be any temperature) into a temperature format dictated by Systems Locale or set by a user in Settings > General > Langauge & Region > Temperature Unit . However, I also need the temperature to display 0 digits after the decimal separator.

So here's a Swift 4 solution (kelvin is the input temperature unit):

func temperatureFormatter(kelvinTemp: Double) -> String{
        let mf = MeasurementFormatter()
        mf.numberFormatter.maximumFractionDigits = 0
        let t = Measurement(value: kelvinTemp, unit: UnitTemperature.kelvin)
        return (String(format:"%@", mf.string(from: t)))
    }

And here you can call your function to convert the values ( _currentTemp is your extracted JSON temperature value & convertedTemp is your resulting converted temperature based on conditions mentioned above):

convertedTemp = temperatureFormatter(kelvinTemp: _currentTemp)

Let me know if this solution works for you, it does work for me though. Thank you!

I wrote a little extension for UnitTemperature to get the unit selected based on the findings from Fábio Oliveira . My use case was knowing which unit the user had selected, not necessarily using it to display something, this is how I did it.

extension UnitTemperature {
  static var current: UnitTemperature {
    let measureFormatter = MeasurementFormatter()
    let measurement = Measurement(value: 0, unit: UnitTemperature.celsius)
    let output = measureFormatter.string(from: measurement)
    return output == "0°C" ? .celsius : .fahrenheit
  }
}

Again, remember to test this using an actual device, not a Simulator.

As others mentioned, the safe way is using MeasurementFormatter directly for formatting if you need to know the current unit! I made this extension, which works in Swift 5 and newer:

extension UnitTemperature {
    static var current: Self {
        let formatter = MeasurementFormatter()
        formatter.locale = .init(identifier: "en_US")
        formatter.unitStyle = .long
        let formatted = formatter.string(from: .init(value: 0, unit: Self.celsius))
        return Self(symbol: formatted.components(separatedBy: " ").last!)
    }
}

You can use it like:

UnitTemperature.current

Note: it may not work on simulator, it works on device

I ended up doing something similar to Amir but a little more robust, ie Amir's solution was failing for me at times.

Instead of instantiating via Self(symbol:) I match the symbol returned when formatting against the three options:

extension UnitTemperature {
  static var current: UnitTemperature {
    let formatter = MeasurementFormatter()
    formatter.locale = Locale.autoupdatingCurrent
    formatter.unitStyle = .medium
    let formatted = formatter.string(from: .init(value: 0, unit: UnitTemperature.celsius))
    let symbol = String(formatted.suffix(2))
    switch (symbol) {
      case UnitTemperature.celsius.symbol:
        return .celsius
      case UnitTemperature.fahrenheit.symbol:
        return .fahrenheit
      default:
        return .kelvin
    }
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM