How can I store a Swift enum value in NSUserDefaults

Swift

Swift Problem Overview


I have an enum like this:

enum Environment {
    case Production
    case Staging
    case Dev
}

And I'd like to save an instance in NSUserDefaults like this:

func saveEnvironment(environment : Environment){
    NSUserDefaults.standardUserDefaults().setObject(environment, forKey: kSavedEnvironmentDefaultsKey)
}

I understand that a Swift enum isn't an NSObject, and that makes it difficult to save, but I'm unsure what the best way is to convert it to something storable.

Swift Solutions


Solution 1 - Swift

Using rawValue for the enum is one way of using types that can be stored in NSUserDefaults, define your enum to use a rawValue. Raw values can be strings, characters, or any of the integer or floating-point number types :

enum Environment: String {
    case Production = "Prod"
    case Staging    = "Stg"
    case Dev        = "Dev"
}

You can also create an enum instance directly using the rawValue (which could come from NSUserDefaults) like:

let env = Environment(rawValue: "Dev")

You can extract the rawValue (String) from the enum object like this and then store it in NSUserDefaults if needed:

if let myEnv = env {
    println(myEnv.rawValue)
}


func saveEnvironment(environment : Environment){
    NSUserDefaults.standardUserDefaults().setObject(environment.rawValue, forKey: kSavedEnvironmentDefaultsKey)
}

Solution 2 - Swift

If you would like to save/read data from UserDefaults and separate some logic, you can do it in following way (Swift 3):

enum Environment: String {
    case Production
    case Staging
    case Dev
}

class UserDefaultsManager {
    
    static let shared = UserDefaultsManager()
    
    var environment: Environment? {
       get {
           guard let environment = UserDefaults.standard.value(forKey: kSavedEnvironmentDefaultsKey) as? String else {
               return nil
           }
           return Environment(rawValue: environment)
       }
       set(environment) {
           UserDefaults.standard.set(environment?.rawValue, forKey: kSavedEnvironmentDefaultsKey)
       }
    }
}

So saving data in UserDefaults will look this way:

UserDefaultsManager.shared.environment = Environment.Production

And reading data, saved in UserDefaults in this way:

if let environment = UserDefaultsManager.shared.environment {
    //you can do some magic with this variable
} else {
    print("environment data not saved in UserDefaults")
}

Solution 3 - Swift

Using Codable protocol

Extent Environment enum that conforms to Codable protocol to encode and decode values as Data.

enum Environment: String, Codable {
    case Production
    case Staging
    case Dev
}

A wrapper for UserDefaults:

struct UserDefaultsManager {
    static var userDefaults: UserDefaults = .standard
    
    static func set<T>(_ value: T, forKey: String) where T: Encodable {
        if let encoded = try? JSONEncoder().encode(value) {
            userDefaults.set(encoded, forKey: forKey)
        }
    }
    
    static func get<T>(forKey: String) -> T? where T: Decodable {
        guard let data = userDefaults.value(forKey: forKey) as? Data,
            let decodedData = try? JSONDecoder().decode(T.self, from: data)
            else { return nil }
        return decodedData
    }
}
Usage
// Set
let environment: Environment = .Production
UserDefaultsManager.set(environment, forKey: "environment")

// Get
let environment: Environment? = UserDefaultsManager.get(forKey: "environment")

Solution 4 - Swift

Swift 5.1 You can create a generic property wrapper, using Codable to transform values in and out the UserDefaults

extension UserDefaults {
    // let value: Value already set somewhere
    // UserDefaults.standard.set(newValue, forKey: "foo")
    //
    func set<T>(_ value: T, forKey: String) where T: Encodable {
        if let encoded = try? JSONEncoder().encode(value) {
            setValue(encoded, forKey: forKey)
        }
    }

    // let value: Value? = UserDefaults.standard.get(forKey: "foo")
    //
    func get<T>(forKey: String) -> T? where T: Decodable {
        guard let data = value(forKey: forKey) as? Data,
            let decodedData = try? JSONDecoder().decode(T.self, from: data)
            else { return nil }
        return decodedData
    }
}

@propertyWrapper
public struct UserDefaultsBacked<Value>: Equatable where Value: Equatable, Value: Codable {
    let key: String
    let defaultValue: Value
    var storage: UserDefaults = .standard

    public init(key: String, defaultValue: Value) {
        self.key = key
        self.defaultValue = defaultValue
    }

    // if the value is nil return defaultValue
    // if the value empty return defaultValue
    // otherwise return the value
    //
    public var wrappedValue: Value {
        get {
            let value: Value? = storage.get(forKey: key)
            if let stringValue = value as? String, stringValue.isEmpty {
                // for string values we want to equate nil with empty string as well
                return defaultValue
            }
            return value ?? defaultValue
        }
        set {
            storage.set(newValue, forKey: key)
            storage.synchronize()
        }
    }
}

// use it
struct AppState: Equatable {
    enum TabItem: String, Codable {
        case home
        case book
        case trips
        case account
    }
    var isAppReady = false

    @UserDefaultsBacked(key: "selectedTab", defaultValue: TabItem.home)
    var selectedTab
    // default value will be TabItem.home

    @UserDefaultsBacked(key: "selectedIndex", defaultValue: 33)
    var selectedIndex
    // default value will be 33

}

Solution 5 - Swift

Here is another alternative that can be be easily used with enums based on types (like String, Int etc) that can be stored by NSUserDefaults.

@propertyWrapper
struct StoredProperty<T: RawRepresentable> {
    let key: String
    let defaultValue: T

    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }

    var wrappedValue: T {
        get {
            guard let rawValue = UserDefaults.standard.object(forKey: key) as? T.RawValue, let value = T(rawValue: rawValue) else {
                 return defaultValue
            }
            return value
        }
        set {
            UserDefaults.standard.set(newValue.rawValue, forKey: key)
        }
    }
}

Example usage:

enum Environment: String {
    case Production
    case Staging
    case Dev
}

@StoredProperty("Environment", defaultValue: .Dev)
var storedProperty: Environment

Solution 6 - Swift

I am using like this type staging. Can you please try this it will help you.

enum Environment: String {
  case Production  = "Production URL"
  case Testing     = "Testing URl"
  case Development = "Development URL"
}
//your button actions
 // MARK: set Development api
  @IBAction func didTapDevelopmentAction(_ sender: Any) {
    let env = Environment.Development.rawValue
    print(env)
    UserDefaults.standard.set(env, forKey:Key.UserDefaults.stagingURL)
  }
// MARK: set Production api
  @IBAction func didTapProductionAction(_ sender: Any) {
    let env = Environment.Production.rawValue
    print(env)
    UserDefaults.standard.set(env, forKey:Key.UserDefaults.stagingURL)
  }
  
  // MARK: set Testing api
  @IBAction func didTapTestingAction(_ sender: Any) {
    let env = Environment.Testing.rawValue
    print(env)
    UserDefaults.standard.set(env, forKey:Key.UserDefaults.stagingURL)
  }
//Based on selection act
print("\(UserDefaults.standard.object(forKey: "stagingURL") ?? "")")

Solution 7 - Swift

Swift 5.1 You can create property wrapper for this

@propertyWrapper final class UserDefaultsLanguageValue {
    var defaultValue: LanguageType
    var key: UserDefaultsKey

    init(key: UserDefaultsKey, defaultValue: LanguageType) {
        self.key = key
        self.defaultValue = defaultValue
    }

    var wrappedValue: LanguageType {
        get { LanguageType(rawValue: UserDefaults.standard.object(forKey: key.rawValue) as? String ?? defaultValue.rawValue) ?? .en }
        set { UserDefaults.standard.set(newValue.rawValue, forKey: key.rawValue) }
    }
}

enum UserDefaultsKey: String {
    case language
}

enum LanguageType: String {
    case en
    case ar
}

And use it just like that

@UserDefaultsLanguageValue(key: .language, defaultValue: LanguageType.en) var language

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionrobView Question on Stackoverflow
Solution 1 - Swiftuser3435374View Answer on Stackoverflow
Solution 2 - SwiftmkkrolikView Answer on Stackoverflow
Solution 3 - SwiftOmer Faruk OzturkView Answer on Stackoverflow
Solution 4 - SwiftKlajd DedaView Answer on Stackoverflow
Solution 5 - SwiftJosephView Answer on Stackoverflow
Solution 6 - SwiftKarthick CView Answer on Stackoverflow
Solution 7 - SwiftrelamView Answer on Stackoverflow