DEV Community

Takahiro Suzuki
Takahiro Suzuki

Posted on • Edited 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)

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay