🔍 Extending Supported Data Types for @AppStorage

TL;DR: Extend SwiftUI’s @AppStorage to handle Date, arrays, and custom enums using the RawRepresentable protocol. Ensure the data type’s RawValue is Int or String, and leverage JSON encoding/decoding for seamless storage and retrieval.

Background

@AppStorage is a property wrapper in SwiftUI that allows easy saving and reading of variables in UserDefaults. However, its default supported data types are limited, excluding commonly used types like Date and arrays.

This article demonstrates how to extend the supported types for @AppStorage, enabling it to handle more custom data types.

Solution

Although @AppStorage natively supports limited types, its flexibility increases when paired with the RawRepresentable protocol. Any data type conforming to RawRepresentable with a RawValue of Int or String can be stored using @AppStorage.

Below are extensions for common data types.

1. Support for Date

By implementing the RawRepresentable protocol for Date, it becomes compatible with @AppStorage:

Swift
extension Date: RawRepresentable {
    public typealias RawValue = String
    
    public init?(rawValue: RawValue) {
        guard let data = rawValue.data(using: .utf8),
              let date = try? JSONDecoder().decode(Date.self, from: data) else {
            return nil
        }
        self = date
    }

    public var rawValue: RawValue {
        guard let data = try? JSONEncoder().encode(self),
              let result = String(data: data, encoding: .utf8) else {
            return ""
        }
        return result
    }
}

Usage is identical to native types:

Swift
@AppStorage("date") var date = Date()

2. Support for Arrays

To support arrays, a generic implementation of RawRepresentable is needed, requiring the array’s elements to conform to the Codable protocol:

Swift
extension Array: RawRepresentable where Element: Codable {
    public init?(rawValue: String) {
        guard let data = rawValue.data(using: .utf8),
              let result = try? JSONDecoder().decode([Element].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
    }
}

Example usage:

Swift
@AppStorage("selections") var selections = [3, 4, 5]

3. Support for Enums

Enums with RawValue of Int or String are already supported natively by @AppStorage, requiring no additional implementation:

Swift
enum Options: Int {
    case a, b, c, d
}

@AppStorage("option") var option = Options.a

Considerations

  • Ensure the extended data types can be correctly encoded and decoded to avoid errors during storage and retrieval.
  • For complex data types, JSON encoding is recommended for greater flexibility.

Further Reading

Explore these articles for additional context and practical examples of SwiftUI property wrappers and their extensions:

Get weekly handpicked updates on Swift and SwiftUI!