DEV Community

Takahiro Suzuki
Takahiro Suzuki

Posted on • Updated on

Implement AppStorage for Dictionary by using Property Wrappers

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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Now we can use Dictionary type of UserDefaults more easily in SwiftUI!

References

This post was translated from Original Post (Japanese)

Top comments (0)