如何在 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)))
}
}
}
}
@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)))
}
}
}
}