bounty 5天后到期。这个问题的答案有资格获得+200的声誉赏金。Jan想要引起更多的关注这个问题:解释为什么当前的方法不起作用并提供解决方案,或者数据的处理/传递是否应该以完全不同的方式设计。如果应该更改数据传递方式的体系结构,还请查看当前的GitHub repo,以了解TrelloApi:github.com/Rukenshia/trello的真正实现
我是Swift/SwiftUI的新手,我正在尝试构建一个与Trello API一起工作的应用程序。
在整个应用程序中有一个“TrelloApi”类,它以@EnvironmentObject
的形式提供。同样的类也用于进行API调用。
一次只能查看一块板子。一块板有许多名单,每个名单有许多卡片。
现在我的渲染有一个问题,每当我切换电路板时,新电路板中的任何列表中的卡都比以前少,我在onReceive
处理程序中得到以下错误,其中我需要执行一些检查来更新卡的外观:
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
模型
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;
}
Swift(为简单起见删除了HTTP调用)
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)
}
}
我已经用评论突出了坠机地点。我没有在ForEach
或List
中传递任何索引,并且我覆盖了整个trelloApi.board
对象,所以我不确定为什么会收到这个错误。
我尝试在SwiftUI.List中使用ForEach,但这也不会改变任何事情。
最小的可重现代码也可以在我的GitHub repo:https://github.com/Rukenshia/trello/tree/troubleshooting-board-switch-crash/trello上找到
1条答案
按热度按时间xvw2m8pv1#
确切的问题很难追踪,但这里有一些观察和建议。
您正在使用的
.onReceive()
修饰符看起来可疑,因为您自己在函数调用中内联地初始化了发布者。通常使用.onReceive()
来响应由另一段代码设置的发布者发布的事件。此外,您将使用此
.onReceive()
来对@Binding
属性中的更改做出React,这是多余的,因为根据定义,@Binding
已经在其值更改时触发了视图更新。编辑
这似乎是导致你的应用程序崩溃的问题。将
.onReceive()
改为.onChange()
似乎可以解决问题:你似乎还复制了一些状态:
这里,您复制了截止日期,在
self.due
中有一个副本,在self.card.dueDate
中有另一个副本。在SwiftUI中,应该只有一个真值来源,对于您来说,它将是card
属性。您复制了init
:self.due = card.wrappedValue.dueDate
中的状态。访问@Binding/State
的.wrappedValue
是一种代码气味,也是您做错了事情的标志。最后,您使用了一个可能很危险的反模式:
您应该避免自己在视图的
init
中初始化@State
属性 Package 。@State
属性必须内联初始化:如果出于某种原因必须自定义
@State
属性的值,则可以在创建视图后使用.onAppear()
或较新的.task()
视图修饰符更改其值:一般来说,你应该把你的观点分解成更小的部分。当一个视图依赖于许多
@State
属性并具有许多.onChange()
或.onReceive()
时,通常表示是时候将整个逻辑移入ObservableObject
或重构到更小的组件中了。