🔍 增加 @AppStorage 支持的数据类型

TL;DR: 使用 RawRepresentable 协议扩展 SwiftUI 的 @AppStorage 支持,处理更多数据类型,如 Date、数组和自定义枚举。确保数据类型的 RawValueIntString,可通过 JSON 编解码实现灵活的存储和读取。

背景

@AppStorage 是 SwiftUI 提供的属性包装器,可用于在视图中便捷地保存和读取 UserDefaults 中的变量。然而,默认情况下,@AppStorage 支持的数据类型较为有限,例如常见的 Date 类型和数组都不在支持范围内。

本文将介绍如何扩展 @AppStorage 的支持类型,使其能够处理更多自定义数据类型。

解决方案

尽管 @AppStorage 原生支持的类型有限,但通过 RawRepresentable 协议,它能够灵活地存储更多自定义数据类型,有效拓展了其存储能力。只要数据类型符合 RawRepresentable 协议,并且其 RawValueIntString,就可以被 @AppStorage 支持。

以下是针对几种常用数据类型的扩展方法。

1. 支持 Date 类型

通过为 Date 类型实现 RawRepresentable 协议,可以让它兼容 @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
    }
}

使用方式与原生支持的类型一致:

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

2. 支持数组类型

为支持数组类型,需要对数组实现泛型版本的 RawRepresentable,要求数组的元素符合 Codable 协议:

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
    }
}

使用示例:

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

3. 支持枚举类型

如果枚举的 RawValueIntString,则无需额外实现扩展,@AppStorage 已经可以直接支持:

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

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

注意事项

  • 确保扩展的数据类型能正确编码和解码,以避免存储和读取数据时发生错误。
  • 为复杂数据类型实现 RawRepresentable 时,推荐使用 JSON 编码,以便处理更加灵活。

延伸阅读

以下文章对 SwiftUI 属性包装器及其扩展应用提供了更多背景信息和实践参考:

为您每周带来有关 Swift 和 SwiftUI 的精选资讯!