如何在 SwiftUI 的@AppStorage 中存储嵌套数组

How to Store Nested Arrays in @AppStorage for SwiftUI

@AppStorage 是最近在 SwiftUI 中引入的,它似乎是 UserDefaults 的替代品。 我正在尝试使 @AppStorage 能够存储嵌套列表。

对于简单的情况,你会做

@AppStorage("selected") var selected = 0

我在处理普通 UserDefaults 时使用了它:

@Published var list = UserDefaults.standard.array(forKey: "nestedList") as? [[String]] ?? [[String]]()

长话短说,如何将普通的旧 UserDefuaults 转换为新的 属性 包装器 @AppStorage

SwiftUI 2.0(Xcode 12 - 将来可能会更改)

AppStorage wrapper 现在不支持容器,只支持 Bool, Int, Double, String, URL, Data.

因此,针对您的案例的解决方案是继续使用 UserDefaults 或将 encode/decode 嵌套数组放入 JSON Data 并使用 AppStorage with Data。

您可以使用数据来实现:

class Storage: NSObject {
    
    static func archiveStringArray(object : [String]) -> Data {
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: false)
            return data
        } catch {
            fatalError("Can't encode data: \(error)")
        }

    }

    static func loadStringArray(data: Data) -> [String] {
        do {
            guard let array = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [String] else {
                return []
            }
            return array
        } catch {
            fatalError("loadWStringArray - Can't encode data: \(error)")
        }
    }
}

struct TestView: View {
            
    @AppStorage("albums") var albums: Data = Data()
    
    var body: some View {
        TabView {
            VStack {
                Text("AppStorage - array String")
                List {
                    ForEach (getStrings(data: albums), id:\.self) { s in
                        Text(s)
                    }
                }
            }
                .tabItem {
                    Text("read only")
                }
            VStack {
                Text("AppStorage - array String")
                List {
                    ForEach (getStrings(data: albums), id:\.self) { s in
                        Text(s)
                    }
                }
                Button("add album") {
                    addAlbum()
                }
            }
                .tabItem {
                    Text("add album")
                }
        }
    }
    
    func getStrings(data: Data) -> [String] {
        return Storage.loadStringArray(data: data)
    }
    func addAlbum() {
        var tmpAlbums = getStrings(data: albums)
        
        tmpAlbums.append("Album # \(tmpAlbums.count)")
        
        albums = Storage.archiveStringArray(object: tmpAlbums)
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
    }
}

您只需使用 :

的扩展名使 Array 符合 RawRepresentable
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
    }
}

这是一个演示:

struct ContentView: View {
    @AppStorage("items") var items: [[String]] = [
        ["1", "2"],
        ["a", "b", "c"],
    ]

    var body: some View {
        VStack {
            Text("items: \(String(describing: items))")
            Button("Add item") {
                items[0].append(String(Int.random(in: 1...10)))
            }
        }
    }
}