SwiftUI:编码要保存在 AppStorage 中的结构

问题描述

目前正在尝试在 swiftUI 中构建我的第一个应用程序。我认为最容易成为噩梦的部分...在 AppStorage 中保存一个结构体,以便在重新启动应用程序时可用

我有两个结构要保存。第一个是针对玩家的,我已经实现了 RawRepresentable

struct Player: Codable,Identifiable {
    let id: Int
    let name: String
    let gamePlayed: Int
    let bestscore: Int
    let nbrGameWon: Int
    let nbrGameLost: Int
    let totalscore: Int?

}

typealias PlayerList = [Player]
extension PlayerList: RawRepresentable {
    public init?(rawValue: String) {
        guard let data = rawValue.data(using: .utf8),let result = try? JSONDecoder().decode(PlayerList.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 AddplayerView: View {
    @State var name: String = ""
    @State var isdisabled: Bool = false
    @State var modified: Bool = false
    @AppStorage("players") var players: PlayerList = PlayerList()
    ...
}

以上工作,现在我也想保存当前的游戏数据,我有以下结构:

struct Game: Codable,Identifiable {
    var id: Int
    var currentPlayerIndexes: Int
    var currentRoundindex: Int?
    var dealerIndex: Int?
    var maxRounds: Int?
    var dealResults: [Int: Array<PlayerRoundSelection>]?
    var currentleaderIds: Array<Int>?
    var isGameInProgress: Bool?
}

extension Game: RawRepresentable {
    public init?(rawValue: String) {
        if rawValue == "" {
            // did to fix issue when calling AppStorage,but it is probably a bad idea
            self = Game(id:1,currentPlayerIndexes:1)
        }
        else {
            guard let data = rawValue.data(using: .utf8),let result = try? JSONDecoder().decode(Game.self,from: data)
            else {
                return nil
            }
            self = result
        }
    }

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

一旦我尝试修改结构,它就会调用 rawValue 并且编码失败并显示以下内容

error: warning: Couldn't get required object pointer (substituting NULL): Couldn't load 'self' because its value Couldn't be evaluated

error: Execution was interrupted,reason: EXC_BAD_ACCESS (code=2,address=0x7ffee49bbff8).

这里是访问结构体的部分代码

struct SelectPlayersView: View {
    @AppStorage("currentGame") var currentGame: Game = Game(rawValue: "")!
    ....
NavigationLink(
                    destination: SelectModeTypeView(),tag: 2,selection: self.$selection) {
                    ActionButtonView(text:"Next",disabled: self.$isdisabled,buttonAction: {
                        var currentPlayers = Array<Int>()
                        self.players.forEach({ player in
                            if selectedplayers.contains(player.id) {
                                currentPlayers.insert(player.id,at: currentPlayers.count)
                            }
                        })
    // This used to be a list of indexes,but for testing only using a single index
    self.currentGame.currentPlayerIndexes = 6
                        self.selection = 2
                    })
...

在这里找到了要编码的代码https://lostmoa.com/blog/SaveCustomCodableTypesInAppStorageOrSceneStorage/

我的理解是,使用编码中的 self,它会生成一个无限循环,因此访问错误

我真的不知道如何正确编码,任何帮助,链接将不胜感激

解决方法

我遇到了同样的问题,我想在这里分享我的经验。

我最终发现,当与 RawRepresentable 结合使用时,显然你不能依赖默认的 Codable 协议实现。 因此,当我使用 CodingKeys 和所有工具进行自己的 Codable 实现时,它奏效了!

我认为您对游戏的 Codable 实现将类似于:

enum CodingKeys: CodingKey {
    case currentPlayerIndexes
    case currentRoundIndex
    // <all the other elements too>
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.currentPlayerIndexes = try container.decode(Int.self,forKey: .currentPlayerIndexes)
    self.currentRoundIndex = try container.decode(Int.self,forKey: .currentRoundIndex)
    // <and so on>
}

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(currentPlayerIndexes,forKey: .currentPlayerIndexes)
    try container.encode(currentRoundIndex,forKey: .currentRoundIndex)
    // <and so on>
}

然后我想知道为什么您的播放器编码/解码确实有效,并发现数组(即 PlayerList,即 [Player])的默认编码和解码工作正常。