Implement AppStorage for Dictionary by using Property Wrappers
Tags: swiftui, propertywrappers, userdefaults, ios
Published: 2022-12-29 14:14:39

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.

```swift
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.

```swift
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.

![Screen Capture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vfvt8zjq1phk90lcuq7t.gif)

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)](https://sussan-po.com/2022/12/29/swiftui-dictionary-appstorage/)