SwiftUI ForEach 抛出索引错误

SwiftUI ForEach Throws out of index error

提问人:David Kababyan 提问时间:11/12/2023 最后编辑:David Kababyan 更新时间:11/13/2023 访问量:84

问:

我似乎不明白为什么这会引发错误? 每个循环的第一个都运行良好,第二个循环抛出错误。 我需要使用数组的索引来获取代码的自定义颜色。否则我会选择普通的 ForEach。

    @ViewBuilder
    private func chartLegendView() -> some View {
        if !viewModel.topTransactions.isEmpty {
            VStack {
                ForEach(viewModel.topTransactions, id: \.self) { transaction in
                    Text(transaction.description)
                }
                ForEach(viewModel.topTransactions.indices, id: \.self) { index in
                    Text(viewModel.topTransactions[index].description)
                }
            }
        }
    }

enter image description here

我已经尝试了所有逻辑选项,但没有任何效果。

更新1:这就是我正在努力实现的目标。 我从 SwiftData 获取一组事务。 我正在从该事务数组(按类别分组和其他一些计算)创建一个自定义对象,并将它们显示在 SwiftUI 图表中。我所拥有的是我的图表图例和图表颜色是自定义的。

我预定义了 10 种颜色,要在数组中的图表上使用。这就是为什么我使用索引从图表中的每个项目中获取颜色的原因。

我的用户可以选择“收入、支出或日期”来筛选来自 SwiftData 的数据并重新生成 UI。我确实同意这个问题看起来像一个模型/获取问题。

这就是我“解决”问题的方式,但它是错误的。当过滤器更换时,我仍然崩溃。

@ViewBuilder
private func chartLegendView() -> some View {
    VStack {
        ForEach(Array(viewModel.topTransactions.prefix(analyticsTransactionsNumber).enumerated()), id: \.element) { index, transaction in
            TopExpenseRow(topExpense: transaction, color: categoryColors[index])

        }
    }
}

struct TopTransaction: Comparable, Hashable, Identifiable {
let id = UUID()
let total: Double
let percentOfTotal: Double
let category: Category?

var description: String {
    "\(name) \(percentOfTotal.noDecimalString)%"
}

var name: String {
    category?.name ?? "Unknown"
}

static func < (lhs: TopTransaction, rhs: TopTransaction) -> Bool {
    return lhs.total > rhs.total
}

func hash(into hasher: inout Hasher) {
    hasher.combine(id)
    hasher.combine(name)
    hasher.combine(total)
    hasher.combine(percentOfTotal)
}

}

enter image description here

SwiftUI foreach

评论

0赞 lorem ipsum 11/12/2023
索引在 ForEach 中是不安全的,您不应该使用它。但枚举似乎更稳定一些
0赞 lorem ipsum 11/12/2023
stackoverflow.com/questions/72222014/......
0赞 lorem ipsum 11/14/2023
不要使用,尤其是与不相关的数组一起使用,因为没有办法告诉 SwiftUI 有变化,在你到达indexForEach

答:

-2赞 malhal 11/12/2023 #1

很多错误:

  1. 删除,这就是什么和是为什么,所以做一个新的自定义。@ViewBuilderViewbodyChartLegendView
  2. Remove ,这会生成代码中不必要的。if_ConditionalView
  3. 删除 ,您需要带有 的真实 id,或者更好地使您的模型结构符合可识别。id: \.selfForEach
  4. 不要在 ForEach 的闭包中使用索引或按索引访问数组。索引在变化中不是唯一的,例如,索引 0 处的交易将获得 ID 0,这与索引 0 处的任何其他交易相同,因此当有多个事务时,它无法检测到索引 0 处的事务被删除,您需要使用真实 ID。

所以它应该看起来像这样:

struct ChartLegendView: View {

    let transactions: [Transaction]
    var body: View {    
        ForEach(transactions) { transaction in
             Text(transaction.description)
        }
    }
}

struct Transaction: Identifiable {
    var id: String {
        return a unique combination of vars, e.g. user + transactionID
    }
...
1赞 Yasha 11/12/2023 #2

您的函数看起来不错,问题出在您的数据模型中。如果不研究您的模型结构,我无法假设解决方案,但这里有一个与您的示例相似的示例,该示例适用于有索引和无索引。@ViewBuilderforEach

struct Thing: Hashable {
  var name: String
  init(_ name: String) {
    self.name = name
  }
}

struct Test: View {
  @State var data = [Thing("A"), Thing("B"), Thing("C"), Thing("D"), Thing("E"), Thing("F")]
  @ViewBuilder
  private func chartLegendView() -> some View {
    VStack {
      ForEach(data, id: \.self) { thing in
        Text(thing.name)
      }
      ForEach(data.indices, id: \.self) { index in
        Text(data[index].name)
      }
    }
  }
  var body: some View {
    chartLegendView()
  }
}

enter image description here

除非数据源是静态的,否则最好不要使用索引,因为它包含对集合的强引用。

enter image description here

评论

0赞 lorem ipsum 11/12/2023
索引不安全 “揭开 SwiftUI 的神秘面纱”将此错误归因于
0赞 Yasha 11/13/2023
这是真的,除非数据源是静态的,否则最好不要使用索引。
0赞 lorem ipsum 11/13/2023
在您的例子中,由于数组处于状态,因此它不是静态的
0赞 David Kababyan 11/13/2023
我已经用额外的信息更新了问题。我确实同意这个问题更像是一个模型。我猜最好的选择是将颜色直接添加到我的自定义 TopTransaction 对象并使用普通的 ForEachLoop
0赞 David Kababyan 11/14/2023
谢谢@Yasha的回答。我更新了我最初的问题