Сейчас пытаюсь создать свое первое приложение в swiftUI. Часть, которая, как я думал, будет самой простой, превратилась в кошмар… сохранить struct в 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),
            let result = String(data: data, encoding: .utf8)
        else {
            return ""
        }
        return result
    }
}

Как только я пытаюсь изменить struct, он вызывает 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:

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 в кодировке, это порождает бесконечный цикл, следовательно, плохой доступ.

Я действительно не знаю, как правильно кодировать это, любая помощь, ссылки были бы признательны.

Nic Laforge

Ответов: 1

Ответы (1)

У меня была такая же проблема, и я хотел поделиться своим опытом здесь.

В конце концов я обнаружил, что, по-видимому, вы не можете полагаться на реализацию протокола Codable по умолчанию при использовании в сочетании с RawRepresentable. Поэтому, когда я сделал свою собственную реализацию Codable, с CodingKeys и всем остальным, она сработала!

Я думаю, что ваша реализация Codable для Game была бы чем-то вроде:

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

Затем я задался вопросом, почему ваша кодировка/декодировка Player работает и обнаружил, что стандартная кодировка и декодировка массива (т.е. PlayerList, который является [Player]), работает нормально.

2022 WebDevInsider