Фон
Впервые я пишу полное приложение на 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'
.
Я включил весь код, который я считаю уместным выше, но дайте мне знать, если вам нужно что-то еще помочь. Как всегда, заранее спасибо за любую помощь, которую вы можете предоставить.