Saving Custom Codable Types in AppStorage or SceneStorage in SwiftUI

KD Knowledge Diet
3 min readMay 20, 2024

Since iOS 14, SwiftUI introduced two property wrappers for persisting state: AppStorage and SceneStorage. While these property wrappers work seamlessly with common value types like Bool, Int, Double, String, URL, and Data, saving custom Codable types requires a bit more effort. In this article, we’ll explore how to persist custom Codable types using AppStorage, allowing you to store more complex data types in your SwiftUI app.

Using AppStorage and SceneStorage:

AppStorage is commonly used to save user-specific settings, while SceneStorage is designed for state restoration. These property wrappers are ideal for storing small pieces of data but might require additional work to persist custom types.

struct ContentView: View {
@AppStorage("city") var city = ""
var body: some View {
TextField("Enter your city", text: $city)
}
}

By default, AppStorage and SceneStorage support types like Bool, Int, Double, String, URL, and Data. However, for custom types, we need to make them conform to RawRepresentable, where the RawValue is of type Int or String.

Custom Codable Type:

Let’s consider an example of a recipe app where users can pin up to three recipes to the top of their list. The user’s pinned recipes are a setting that needs to be persisted throughout the app, making AppStorage suitable for our use case.

Our custom type, PinnedRecipes, is an array of UUIDs and is Codable by default. If your custom type is not Codable, make sure to add Codable conformance manually.

typealias PinnedRecipes = [UUID]
extension PinnedRecipes: RawRepresentable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(PinnedRecipes.self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
}

With RawRepresentable conformance in place, we can now use PinnedRecipes with AppStorage or SceneStorage.

struct ContentView: View {
@AppStorage("pinnedRecipes") var pinnedRecipes = PinnedRecipes()

var body: some View {
List {
Section(header: Text("Pinned Recipes")) {
ForEach(pinnedRecipes, id: \.self) { id in
// Display pinned recipes
}
}
}
}
}

This approach allows you to save custom structs, dictionaries, or enums with associated values in AppStorage or SceneStorage.

Conclusion:

AppStorage and SceneStorage in SwiftUI provide convenient ways to persist app state. While they are best suited for simple data types, you can use RawRepresentable conformance to store custom Codable types. This flexibility enables you to save more complex data structures in your SwiftUI app settings or view states. Keep in mind that AppStorage and SceneStorage are not a replacement for a full-fledged database but are designed for small, app-specific data.

You can find the full source code for this article on GitHub, providing a practical example of saving custom Codable types using AppStorage in SwiftUI. Happy coding! 🚀

--

--

KD Knowledge Diet

Software Engineer, Mobile Developer living in Seoul. I hate people using difficult words. Why not using simple words? Keep It Simple Stupid!