Использование массива объектов дает, по-видимому, некорректный индекс за пределами ошибки в ContentView - PullRequest
0 голосов
/ 10 апреля 2020

Фон

Впервые я пишу полное приложение на SwiftUI или Swift вообще по этому вопросу. Я использую SwiftUI, чтобы попытаться создать простое приложение «выбери себе приключение». Он в основном включает в себя направленный граф, где каждый узел может иметь или не иметь связанных с ним «решений» и может вести к одному или нескольким другим узлам в зависимости от решения, выбранного пользователем. Текст узла отображается на экране, и если он принимает решения, его текст отображается в виде двух кнопок. В зависимости от того, что выберет пользователь, на экран выводится текст другого узла (если не принято решение, предполагается, что он автоматически получит следующий узел - не понял, но это не является целью этого вопроса, я просто использую кнопка-заполнитель без текста, который нужно нажать, чтобы перейти в случае, если решения не будут приняты).

Структура

Помимо начальных файлов: у меня есть класс Node , Decision struct и Game struct.

Узел
An ObservableObject с тремя релевантными свойствами: String для текста, Decision массив для решений и массив @Published Node для дочерних элементов.

Также имеется соответствующая функция под названием addChild(child: Node), которая добавляет узел в массив дочерних элементов.

class Node: ObservableObject {
    let id: Double
    let text: String
    let decisions: [Decision]
    let speaker: Int
    @Published var children: [Node] = []

    init(id: Double, text: String, decisions: [Decision], speaker: Int) {
        self.id = id
        self.text = text
        self.decisions = decisions
        self.speaker = speaker
    }

    func addChild(child: Node) {
        print("Added a child")
        self.children.append(child)
    }


    func getChild(whichChild: Int) -> Node {
        return self.children[whichChild]
    }

    func hasDecisions() -> Bool {
        return (decisions.count > 0)
    }

    func hasChildren() -> Bool {
        return (children.count > 0)
    }
}

Decision
Имеет два соответствующих свойства: String для текста и Int индекс дочернего элемента, к которому он ведет. Нет методов.

Игра
Имеет только один метод с именем createGame() -> Node, который создает целое дерево истории, инициализируя Node s и Decision s, а затем складывая их вместе, используя метод addChild(child: Node). Он возвращает узел root, который должен содержать все дерево историй.

public struct Game {
    func createGame() -> Node {

        // First 13 Nodes for Testing
        var n1 = Node(id: 1, text: "Just a text node...", decisions: [], speaker: 0)

        let n2d1 = Decision(id: "n2d1", text: "Who is this?", whichChild: 0)
        let n2d2 = Decision(id: "n2d2", text: "Whoa. Is this an actual person?", whichChild: 0)
        let n2d = [n2d1, n2d2]
        var n2 = Node(id: 2, text: "Node with decisions...", decisions: n2d, speaker: 1)

        var n3 = Node(id: 3, text: "Both decisions lead to this node...", decisions: [], speaker: 1)


        n1.addChild(child: n2)
        n2.addChild(child: n3)

        return n1

    }

}

ContentView
Представление содержимого может быть источником моих проблем, вот код для этого:
отображает весь текст, который уже был воспроизведен пользователем, и затем в зависимости от того, что пользователь решит делать дальше, находит следующий Node, текст и решения которого должны отображаться.

struct ContentView: View {

    let root = Game().createGame()

    @ObservedObject private var currentNode = Game().createGame()
    @State private var previouslyPlayed: [String] = []
    @State private var decisionList: [Int] = []

    var body: some View {
        VStack {
            //Loop through previously played, displaying each message
            ForEach(previouslyPlayed, id: \.self) { text in
                Text(text)
            }
            Text(currentNode.text)
            HStack {
                if (currentNode.decisions.count > 0) {
                    ForEach(currentNode.decisions, id: \.text) { decision in
                        Button(decision.text) {
                            self.makeDecision(decision: decision)
                            print(self.decisionList)
                        }
                    }
                } else {
                    Button("Move on...") {
                        self.noDecision()
                        print(self.decisionList)
                    }
                    // self.noDecision()
                }

            }
        }
    }

    mutating func makeDecision(decision: Decision) {

        self.previouslyPlayed.append(self.currentNode.text)
        self.previouslyPlayed.append(decision.text)
        self.decisionList.append(decision.whichChild)
        // Get the next node using decisionList
        var nextNode = root
        for whichChild in self.decisionList {
            nextNode = nextNode.getChild(whichChild: whichChild)
        }

        self.currentNode = nextNode

        // self.previouslyPlayed.append(nextNode.text)

    }

    mutating func noDecision() {
        self.previouslyPlayed.append(self.currentNode.text)
        self.decisionList.append(0)

        var nextNode = root
        print(root.children[0].children)
        for whichChild in self.decisionList {
            print("Which child: \(whichChild)")
            print(nextNode.text)
            print(nextNode.children.count)
            nextNode = nextNode.getChild(whichChild: whichChild)
        }

        self.currentNode = nextNode
    }

}

Проблема

Изначально у меня был Node class как struct, а метод addChild был mutating. В тот момент у меня была проблема с тем, что при игре в игру она будет нормально работать ровно для одного прыжка с узла (например, когда пользователь нажимает кнопку для принятия решения). Но для второго скачка узла он получал бы индекс из-за ошибок границ и не мог найти следующий узел, даже если график был построен правильно и индекс должен был существовать.

Я провел некоторое исследование и обнаружил, что некоторая информация о ObservableObjects и о том, как массивы объектов вызывают проблемы, поэтому я превратил Node в class и сделал массив потомков @Published, потому что это единственная переменная, которая мутирует данные класса. Затем я отметил два метода, которые вызываются, когда пользователь нажимает кнопку (принимает решение) на mutating, поскольку они изменяют свойство @ObservableObject currentNode. Теперь, однако, программа даже не будет собираться из-за ошибки Cannot use mutating member on immutable value: 'self' is immutable'.

Я включил весь код, который я считаю уместным выше, но дайте мне знать, если вам нужно что-то еще помочь. Как всегда, заранее спасибо за любую помощь, которую вы можете предоставить.

1 Ответ

0 голосов
/ 10 апреля 2020

Вот вариант с исправлениями и некоторыми репликами, а также сделан редизайн. Скомпилировано и запущено с Xcode 11.4.

struct Decision {
    let id: String
    let text: String
    let whichChild: Int
}

class Node: ObservableObject {
    let id: Double
    let text: String
    let decisions: [Decision]
    let speaker: Int
    @Published var children: [Node] = []

    init(id: Double, text: String, decisions: [Decision], speaker: Int) {
        self.id = id
        self.text = text
        self.decisions = decisions
        self.speaker = speaker
    }

    func addChild(child: Node) {
        print("Added a child")
        self.children.append(child)
    }


    func getChild(whichChild: Int) -> Node {
        return self.children[whichChild]
    }

    func hasDecisions() -> Bool {
        return (decisions.count > 0)
    }

    func hasChildren() -> Bool {
        return (children.count > 0)
    }
}

public class Game: ObservableObject {
    let root = Game.createGame()
    @Published var currentNode = Game.createGame()

    static func createGame() -> Node {

        // First 13 Nodes for Testing
        var n1 = Node(id: 1, text: "Just a text node...", decisions: [], speaker: 0)

        let n2d1 = Decision(id: "n2d1", text: "Who is this?", whichChild: 0)
        let n2d2 = Decision(id: "n2d2", text: "Whoa. Is this an actual person?", whichChild: 0)
        let n2d = [n2d1, n2d2]
        var n2 = Node(id: 2, text: "Node with decisions...", decisions: n2d, speaker: 1)

        var n3 = Node(id: 3, text: "Both decisions lead to this node...", decisions: [], speaker: 1)


        n1.addChild(child: n2)
        n2.addChild(child: n3)

        return n1

    }

}

struct ContentView: View {
    @ObservedObject var game = Game()

    @State private var previouslyPlayed: [String] = []
    @State private var decisionList: [Int] = []

    var body: some View {
        VStack {
            //Loop through previously played, displaying each message
            ForEach(previouslyPlayed, id: \.self) { text in
                Text(text)
            }
            Text(game.currentNode.text)
            HStack {
                if (game.currentNode.decisions.count > 0) {
                    ForEach(game.currentNode.decisions, id: \.text) { decision in
                        Button(decision.text) {
                            self.makeDecision(decision: decision)
                            print(self.decisionList)
                        }
                    }
                } else {
                    Button("Move on...") {
                        self.noDecision()
                        print(self.decisionList)
                    }
                    // self.noDecision()
                }

            }
        }
    }

    func makeDecision(decision: Decision) {

        self.previouslyPlayed.append(game.currentNode.text)
        self.previouslyPlayed.append(decision.text)
        self.decisionList.append(decision.whichChild)
        // Get the next node using decisionList
        var nextNode = game.root
        for whichChild in self.decisionList {
            nextNode = nextNode.getChild(whichChild: whichChild)
        }

        game.currentNode = nextNode

        // self.previouslyPlayed.append(nextNode.text)

    }

    func noDecision() {
        self.previouslyPlayed.append(game.currentNode.text)
        self.decisionList.append(0)

        var nextNode = game.root
        print(game.root.children[0].children)
        for whichChild in self.decisionList {
            print("Which child: \(whichChild)")
            print(nextNode.text)
            print(nextNode.children.count)
            nextNode = nextNode.getChild(whichChild: whichChild)
        }

        game.currentNode = nextNode
    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...