0

I'm making a weather app, and currently I'm adding support to switch from Imperial measurement to Metric, but when you switch from the settings view controller to the main menu to the weather viewer, it resets the unit variable to Imperial.

I have looked at many questions and articles, and either:

  1. I don't understand how to do it
  2. It doesn't work or needs an older version of Swift
  3. It causes the whole app to break

========================================================================

Note: I am using segues to switch VCs

@IBAction func unitSwitcherMetric(_ sender: UIButton) {

    weatherDataModel.unit = "metric"
    weatherDataModel.windUnit = ""
    print("The unit is now \(weatherDataModel.unit)")

}

After you select that button, it prints The unit is now metric as expected, but when you switch to the main menu view controller, where it's supposed to print the unit variable, it prints imperial and the API supplies imperial measurements as well.

  • what's weatherDataModel? A variable on your setting vc? please explain why you think menu vc should get notice that you changed a variable on the settings vc... are you using a centralized variable to controle that? – jastrada Jun 13 '19 at 21:28
  • I need it to be notified because this is an app-wide change for any API requests – wiggleforlife Jun 13 '19 at 21:30
  • You need to share a single instance of your data model between the two view controllers. How do you show the settings view controller from the main? How do you return to the main from settings? Show that code – Paulw11 Jun 13 '19 at 21:34
  • Segues in main.storyboard – wiggleforlife Jun 13 '19 at 21:37
  • You can use `prepareForSegue` to pass your model instance to the settings view controller. – Paulw11 Jun 13 '19 at 22:37
  • 1
    Also, to keep your settings when the app restarts you will need to persist the setting. You could use Core Data or User Defaults – Paulw11 Jun 13 '19 at 22:41

2 Answers2

1

You could implemente a Singleton, that's a class that has only 1 instance and is meant to be kept in memory for different uses. You can implement it like this:

class MetricManager {

    static let sharedInstance = MetricManager()

    var currentUnit: String = "imperial"

    private init() {

    }
}

After that you can simply assign a value on Settings VC like this

@IBAction func unitSwitcherMetric(_ sender: UIButton) {
    MetricManager.sharedInstance.currentUnit = "metric"
}

And then on Menu or wherever you want to get the current unit value, you just consult it on:

MetricManager.sharedInstance.currentUnit
jastrada
  • 384
  • 1
  • 8
  • Can I add another variable to this or do I have to make a separate class? – wiggleforlife Jun 14 '19 at 10:29
  • Singletons can have as many variables that you want, also functions. You can read more here for example https://medium.com/@nimjea/singleton-class-in-swift-17eef2d01d88 – jastrada Jun 14 '19 at 12:47
0

A couple of thoughts:

  1. I would not suggest using string values. If I needed to support iOS versions prior to 10, I’d define an enumeration:

    enum CustomUnitTemperature: String {
        case celsius
        case fahrenheit
    }
    
  2. In terms of saving this “preferred temperature units” so that it’s available for the next time you run the app, you can save the user’s preferences in UserDefaults, e.g.:

    struct Preferences {
        private let temperatureUnitKey = "customTemperatureUnitKey"
    
        var unitTemperature: CustomUnitTemperature {
            didSet {
                UserDefaults.standard.set(unitTemperature.rawValue, forKey: temperatureUnitKey)
            }
        }
    
        init() {
            unitTemperature = UserDefaults.standard.string(forKey: temperatureUnitKey)
                .flatMap { CustomUnitTemperature(rawValue: $0) } ?? .celsius
        }
    }
    

    Then you could have a preferences object:

    var preferences = Preferences()
    

    You can then read and write the unitTemperature to the preferences (which will persist between runs of the app) like so. E.g. to read it:

    let unit = preferences.unitTemperature
    
    switch unit {
    case .celsius:    ...
    case .fahrenheit: ...
    }
    

    Or, to change/save the value:

    preferences.unitTemperature = .fahrenheit
    

    Bottom line, it’s a property which is initialized by reading from UserDefaults and updated in UserDefaults when you set the property.

  3. If you don’t need to support iOS versions before 10, rather than defining my own temperature unit of measure, I’d use the existing UnitTemperature type. See the Dimension documentation or the WWDC 2016 video Measurements and Units for information about how you might use this UnitTemperature type.

    Anyway, in iOS 10 and later, I might define my Preferences type to access and save using this UnitTemperature type, but saving the symbol to the UserDefaults:

    struct Preferences {
        private let temperatureUnitKey = "temperatureUnitKey"
    
        var unitTemperature: UnitTemperature {
            didSet {
                UserDefaults.standard.set(unitTemperature.symbol, forKey: temperatureUnitKey)
            }
        }
    
        init() {
            unitTemperature = UserDefaults.standard.string(forKey: temperatureUnitKey)
                .map { UnitTemperature(symbol: $0) } ?? .celsius
        }
    }
    

    Then you can access it the same way (though our switch would now need a default clause, since celsius and fahrenheit aren’t the only acceptable values:

    let unit = preferences.unitTemperature
    
    switch unit {
    case .celsius:    ...
    case .fahrenheit: ...
    default:          ...
    }
    

    And updating the preferences are the same:

    preferences.unitTemperature = .fahrenheit
    
  4. In terms of passing data from a presenting view controller to a presented view controller, and back, see Passing Data between View Controllers.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • The problem with using an enumeration is that the unit is a parameter of the API call. I could just convert it with ```if unit == celsius```and then use the farenheit -> celsius formula, but I would prefer if it would already be done in case I have an error in my math. – wiggleforlife Jun 14 '19 at 12:05
  • You can still use enumerations with API calls. E.g. an enumeration with a [raw value](https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html#ID149), like my example above, is still `Codable`. And one of the reasons to consider `UnitTemperature` (for iOS 10 and later only) is that you wouldn’t have to change how your API is called (e.g. do all calls in metric) and let the OS `MeasurementFormatter` and the like do the conversions for you. The whole idea with these measurement types is that you should never do the math yourself. – Rob Jun 14 '19 at 15:18