SwiftUI:删除行时使用 Array/Index 的 ForEach 崩溃

SwiftUI: ForEach using Array/Index crashes when rows are deleted

我看过几篇关于此的帖子,但到目前为止 none 的解决方案似乎对我有用。

我正在尝试使用 ForEach 创建一个可识别项数组——其中包含 Text()Toggle() 视图。该数组存储在 @Published 属性 的 @ObservableObject.

我目前正在遍历索引以创建切换绑定(如 中所建议)。

似乎一切正常,直到我尝试删除一行。

(特别是最后一行 - 每次都会触发 "Fatal error: Index out of range"。)

如有任何帮助,我们将不胜感激!

struct Rule: Identifiable {
  let id: String
  var displayName: String
  var isEnabled: Bool
}

class UserData: ObservableObject {
  @Published var rules: [Rule] = []
}

struct RuleListView: View {
  @ObservableObject var userData: UserData

  var body: some View {
    List {
      ForEach(userData.rules.indices, id: \.self) { index in
        HStack {
          Toggle(
            isOn: self.$userData.rules[index].isEnabled
          ) { Text("Enabled") }
          Text(self.userData.rules[index].displayName)
        }
      }
      .onDelete(perform: delete)
    }
  }

  func delete(at offsets: IndexSet) {
    userData.rules.remove(atOffsets: offsets)
  }
}

看来你的代码复杂了:

class UserData: ObservableObject {
  @Published var rules: [Rule] = []
}

当新元素被添加到 rules 数组时会注意到,你可以通过声明来做到这一点:

@State var rules = [Rule]()

您可能想知道 Rule class 中的 isEnabled 何时更改。现在它没有发生。对于 ObservableObject 必须是 Rule class。

记住这一点,如果您将代码更改为:

import SwiftUI

class Rule: ObservableObject, Identifiable {
  let id: String
  var displayName: String
  @Published var isEnabled: Bool

  init(id: String, displayName: String, isEnabled: Bool) {
    self.id = id
    self.displayName = displayName
    self.isEnabled = isEnabled
  }
}

struct ContentView: View {
  // for demonstration purpose, you may just declare an empty array here
  @State var rules: [Rule] = [
    Rule(id: "0", displayName: "a", isEnabled: true),
    Rule(id: "1", displayName: "b", isEnabled: true),
    Rule(id: "2", displayName: "c", isEnabled: true)
  ]

  var body: some View {
    VStack {
      List {
        ForEach(rules) { rule in
          Row(rule: rule)
        }
        .onDelete(perform: delete)
      }
    }
  }

  func delete(at offsets: IndexSet) {
    rules.remove(atOffsets: offsets)
  }
}

struct Row: View {
  @ObservedObject var rule: Rule

  var body: some View {
    HStack {
      Toggle(isOn: self.$rule.isEnabled)
      { Text("Enabled") }

      Text(rule.displayName)
        .foregroundColor(rule.isEnabled ? Color.green : Color.red)
    }
  }
}

rules数组加入新元素时会通知,isEnabled变化时也会通知。 这也解决了您的崩溃问题。