How to save enum with associated value in UserDefaults using Swift
Table of Contents
The UserDefaults is a database that means to store small key-value pairs kept across launches of your app.
We usually use this to store a user's preferences. For example, you can allow users to specify their preferred light/dark mode.
The problem is only String
, Data
, Number
, Date
, Array
, and Dictionary
are supported.
Enumeration, which is a type that we usually use to represent an option isn't supported.
For example, you might want to define a dark/light mode option as an enum like this:
enum ColorPreference {
case light
case dark
case system
}
We can classify an enum into two categories.
- Enumerations with raw value.
- Enumerations with associated value.
We already discussed the first approach here.
In this article, I will show you how to save enum with associated value in UserDefaults
.
You can easily support sarunw.com by checking out this sponsor.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
How to save enum with associated value in UserDefaults
For demonstration, we create a Role
enum to keep a user's role.
enum Role {
case guest
case member(String?)
}
If we save this enum to UserDefaults, you will get a runtime exception.
UserDefaults.standard.set(
Role.guest,
forKey: "role")
// Attempt to insert non-property list object example_enum_userdefaults.Role.guest for key role (NSInvalidArgumentException)
To read/write enum with associated value to UserDefaults, we need to do two things.
- We make it conform to the
Codable
protocol. - We encode/decode before reading/writing it to UserDefaults.
Make Enumerations conform to Codable protocol
Swift 5.5 introduced Codable synthesis for enums with associated values. You can make an enum with associated values conform to Codable
by simply adopting it.
enum Role: Codable {
case guest
case member(String?)
}
Role.guest
would be encoded to.
{
"guest": {}
}
And Role.member("Sarunw")
would be encoded to.
{
member = {
"_0" = "Sarunw"
};
}
Once the enum conforms to the Codable
protocol, you have two ways to read and write them to UserDefaults
.
Since UserDefautl
supported both Data
and Dictionary
, we can save our enum either in the form of:
- Data
- Dictionary
Let's see how to do it and learn the differences.
Read and write enumerations by Data
We can encode/decode Codable
protocol to/from Data
using JSONEncoder
and JSONDecoder
.
In this example, we read/write our enum to UserDefaults
by converting it to Data
.
let userDefaults = UserDefaults.standard
// Write
if let data = try? JSONEncoder().encode(Role.member("Sarunw")) {
userDefaults.set(
data,
forKey: "role")
}
// Read
if let savedData = userDefaults.data(forKey: "role") {
let role = try? JSONDecoder().decode(Role.self, from: savedData)
}
For writing, we encode our enum to Data
and then save it to UserDefaults
.
For reading, we get back saved Data
and decode back to Role
.
Read and write enum using Data
is straightforward, the only downside is you can't set default value using plist
.
Read and write enumerations by Dictionary
To be able to set default value for enum type using dictionary, you have to do one extra step converting Data
type to Dictionary
.
You can easily do this with the help of JSONSerialization
.
In this example, we read/write our enum to UserDefaults
by converting it to Dictionary
.
let userDefaults = UserDefaults.standard
// Write
if let data = try? JSONEncoder().encode(Role.member("Sarunw")),
let dict = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
{
userDefaults.set(
dict,
forKey: "role")
}
// Read
if let rawDictionary = userDefaults.dictionary(forKey: "role"),
let dictData = try? JSONSerialization.data(withJSONObject: rawDictionary)
{
let role = try? JSONDecoder().decode(Role.self, from: dictData)
}
For writing, we do an extra step converting Data
to Dictionary
using JSONSerialization
before saving it to UserDefaults
.
For reading, we get back saved Dictionary
, converting it to Data
, then decode it back to Role
.
Populate default values from plist
With this approach, you can populate your data from a plist
file.
First, create a plist
file with a dictionary representing an enum you want to be your default value.
Then, load the plist and register it to UserDefaults
.
let userDefaults = UserDefaults.standard
let infoPlistPath = Bundle.main.url(forResource: "defaults", withExtension: "plist")!
let infoPlistData = try! Data(contentsOf: infoPlistPath)
let defaultDict = try! PropertyListSerialization.propertyList(from: infoPlistData, options: [], format: nil) as! [String: Any]
userDefaults.register(defaults: defaultDict)
After that, you can read the default value out the same way.
let userDefaults = UserDefaults.standard
// Read default values from plist
let infoPlistPath = Bundle.main.url(forResource: "defaults", withExtension: "plist")!
let infoPlistData = try! Data(contentsOf: infoPlistPath)
let defaultDict = try! PropertyListSerialization.propertyList(from: infoPlistData, options: [], format: nil) as! [String: Any]
userDefaults.register(defaults: defaultDict)
// Read default role enum
let rawValue = userDefaults.dictionary(forKey: "role")!
let dictData = try! JSONSerialization.data(withJSONObject: rawValue)
let role = try? JSONDecoder().decode(Role.self, from: dictData)
Read more article about Swift, UserDefaults, Enum, or see all available topic
Enjoy the read?
If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.
Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.
If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.
Become a patron Buy me a coffee Tweet ShareCustom Layout in SwiftUI
If you have a layout that the built-in layout like VStack and HStack can't serve, you can create a custom one in iOS 16. Let's learn how to do it.
SwiftUI Gauge
iOS 16 brings a new view to SwiftUI, Gauge. Let's see what it is and how to use it.