Implement AppStorage for Dictionary by using Property Wrappers
Takahiro Suzuki
Posted on December 29, 2022
In SwiftUI, we can access to the UserDefaults by using AppStorage
. However, it is restricted for available types. Especially, Dictionary type is not supported. In this post, I introduce how to implement the property wrapper for storing Dictionary type values like AppStorage
.
Implementation
First, I'll show whole of implementation.
Since this is a "it just happened to work" implementation, I cannot guarantee that it will be optimal in your environment as well.
import SwiftUI
@propertyWrapper
struct DictionaryDefaults: DynamicProperty {
private let key: String
private let userDefaults: UserDefaults
@State private var dictionary: [String: Any] {
didSet {
userDefaults.set(dictionary, forKey: key)
}
}
var wrappedValue: [String: Any] {
get {
dictionary
}
nonmutating set {
dictionary = newValue
}
}
var projectedValue: Binding<[String: Any]> {
Binding {
wrappedValue
} set: { newValue in
wrappedValue = newValue
}
}
init(wrappedValue defaultValue: [String: Any], key: String, suiteName: String? = nil) {
self.key = key
userDefaults = UserDefaults(suiteName: suiteName)!
let dict = userDefaults.dictionary(forKey: key)
if let dict {
dictionary = dict
} else {
userDefaults.set(defaultValue, forKey: key)
dictionary = defaultValue
}
}
}
How to use
For example, I implemented codes below.
struct FirstView: View {
// DictionaryDefaults, introduced in this post
@DictionaryDefaults(key: "my_dict")
private var myDictionary = [String: Any]()
// Second screen flag
@State private var isPresentSecondView = false
// convert Dictionary to Array for display...
private var keys: [String] {
get {
myDictionary.keys.map { $0 }
}
}
var body: some View {
NavigationStack {
List {
ForEach(keys, id: \.self) { key in
Text(myDictionary[key] as! String)
}
}
.toolbar {
// All clear button
Button {
myDictionary.removeAll()
} label: {
Text("Clear")
}
// Add Button
Button {
myDictionary.updateValue(
"FirstView",
forKey: UUID().uuidString
)
} label: {
Text("Add")
}
// Show SecondView Button
Button {
isPresentSecondView.toggle()
} label: {
Text("Show SecondView")
}
}
.sheet(isPresented: $isPresentSecondView) {
// pass the Binding of myDictionary to SecondView
SecondView(dict: $myDictionary)
}
}
}
}
struct SecondView: View {
// Bind the Dictionary
@Binding var dict: [String: Any]
var body: some View {
// Add Button
Button {
dict.updateValue(
"SecondView",
forKey: UUID().uuidString
)
} label: {
Text("Add")
}
// All clear Button
Button {
dict.removeAll()
} label: {
Text("Clear")
}
}
}
Screen Capture
Although ordering is shuffled because the value is the type of Dictionary, as you can see, State and Binding works fine, saving UserDefaults also works.
Now we can use Dictionary type of UserDefaults more easily in SwiftUI!
References
- https://www.hackingwithswift.com/plus/intermediate-swiftui/creating-a-custom-property-wrapper-using-dynamicproperty
- https://software.small-desk.com/development/2020/10/03/swift-propertywrapper-projectedvalue/
- https://software.small-desk.com/development/2020/10/02/swift-propertywrapper-initialize/
- https://www.donnywals.com/writing-custom-property-wrappers-for-swiftui/ https://qiita.com/inuha/items/2f760a0122003a3b90d3
This post was translated from Original Post (Japanese)
Posted on December 29, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.