I am new to Swift/SwiftUI and am trying to build an app that works with the trello API.
There is a "TrelloApi" class that is available as an @EnvironmentObject
in the entire app. The same class is also used to make API calls.
One board is viewed at a time. A board has many lists and each list has many cards.
Now I have an issue with my rendering where whenever I switch boards and any list in the new board has fewer cards in it than before, I get the following error in an onReceive
handler where I need to do some checks to update the cards appearance:
Swift/ContiguousArrayBuffer.swift:575: Fatal error: Index out of range
2022-10-19 09:04:11.319982+0200 trello[97617:17713580] Swift/ContiguousArrayBuffer.swift:575: Fatal error: Index out of range
Models
struct BoardPrefs: Codable {
var backgroundImage: String? = "";
}
struct BasicBoard: Identifiable, Codable {
var id: String;
var name: String;
var prefs: BoardPrefs;
}
struct Board: Identifiable, Codable {
var id: String;
var name: String;
var prefs: BoardPrefs;
var lists: [List] = [];
var cards: [Card] = [];
var labels: [Label] = [];
}
struct List: Identifiable, Codable, Hashable {
var id: String;
var name: String;
var cards: [Card] = [];
private enum CodingKeys: String, CodingKey {
case id
case name
}
}
struct Card: Identifiable, Codable, Hashable {
var id: String;
var idList: String = "";
var labels: [Label] = [];
var idLabels: [String] = [];
var name: String;
var desc: String = "";
var due: String?;
var dueComplete: Bool = false;
}
TrelloApi.swift (HTTP call removed for simplicity)
class TrelloApi: ObservableObject {
let key: String;
let token: String;
@Published var board: Board;
@Published var boards: [BasicBoard];
init(key: String, token: String) {
self.key = key
self.token = token
self.board = Board(id: "", name: "", prefs: BoardPrefs())
self.boards = []
}
func getBoard(id: String, completion: @escaping (Board) -> Void = { board in }) {
if id == "board-1" {
self.board = Board(id: "board-1", name: "board-1", prefs: BoardPrefs(), lists: [
List(id: "board-1-list-1", name: "board-1-list-1", cards: [
Card(id: "b1-l1-card1", name: "b1-l1-card1"),
]),
List(id: "board-1-list-2", name: "board-1-list-2", cards: [
Card(id: "b1-l2-card1", name: "b1-l2-card1"),
Card(id: "b1-l2-card2", name: "b1-l2-card2"),
])
])
completion(self.board)
} else {
self.board = Board(id: "board-2", name: "board-2", prefs: BoardPrefs(), lists: [
List(id: "board-2-list-1", name: "board-2-list-1", cards: [
]),
List(id: "board-2-list-2", name: "board-2-list-2", cards: [
Card(id: "b2-l2-card1", name: "b2-l2-card1"),
])
])
completion(self.board)
}
}
}
ContentView.swift
struct ContentView: View {
@EnvironmentObject var trelloApi: TrelloApi;
var body: some View {
HStack {
VStack {
Text("Switch Board")
Button(action: {
trelloApi.getBoard(id: "board-1")
}) {
Text("board 1")
}
Button(action: {
trelloApi.getBoard(id: "board-2")
}) {
Text("board 2")
}
}
VStack {
ScrollView([.horizontal]) {
ScrollView([.vertical]) {
VStack(){
HStack(alignment: .top) {
ForEach($trelloApi.board.lists) { list in
TrelloListView(list: list)
.fixedSize(horizontal: false, vertical: true)
}
}
.padding()
.frame(maxHeight: .infinity, alignment: .top)
}
}
}
}
}.onAppear {
trelloApi.getBoard(id: "board-1")
}
.frame(minWidth: 900, minHeight: 600, alignment: .top)
}
}
TrelloListView.swift
struct TrelloListView: View {
@EnvironmentObject var trelloApi: TrelloApi;
@Binding var list: List;
var body: some View {
VStack() {
Text(self.list.name)
Divider()
SwiftUI.List(self.$list.cards, id: \.id) { card in
CardView(card: card)
}
.listStyle(.plain)
.frame(minHeight: 200)
}
.padding(4)
.cornerRadius(8)
.frame(minWidth: 200)
}
}
CardView.swift
struct CardView: View {
@EnvironmentObject var trelloApi: TrelloApi;
@Binding var card: Card;
var body: some View {
VStack(alignment: .leading) {
HStack {
VStack(alignment: .leading, spacing: 0) {
Text(card.name)
.bold()
.font(.system(size: 14))
.multilineTextAlignment(.leading)
.lineLimit(1)
.foregroundColor(.white)
Text(card.desc)
.lineLimit(1)
.foregroundColor(.secondary)
}.padding()
Spacer()
}
}
.frame(alignment: .leading)
.onReceive(Just(card)) { newCard in
// CRASH LOCATION: "Index out of range" for self.card.labels
if self.card.labels != newCard.labels {
print("(check if card color should change based on labels)")
}
}
.cornerRadius(4)
}
}
I've highlighted the crash location with a comment. I don't pass any indexes in the ForEach
or List
and I am overwriting the entire trelloApi.board
object, so I am not sure why I am getting this error.
I've tried using ForEach inside of the SwiftUI.List instead, but that also doesn't change anything.
The minimal reproducible code can also be found on my GitHub repo: https://github.com/Rukenshia/trello/tree/troubleshooting-board-switch-crash/trello