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

我与影子孤独终老i 提交于 2020-05-09 05:59:49

问题


I've seen several posts about this, but so far none of the solutions seem to be working for me.

I'm trying to create an array of Identifiable items using ForEach -- with both a Text() and Toggle() view inside. The array is stored in a @Published property of an @ObservableObject.

I'm currently looping through the indices to create the toggle bindings (as suggested in other posts).

Everything appears to be working, until I try to delete a row.

(Specifically the last row - which triggers a "Fatal error: Index out of range" every time.)

Any help would be greatly appreciated!

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)
  }
}


回答1:


It seems you have complicated your code:

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

Will notice when new element is added to rules array, you could have done that just by declaring:

@State var rules = [Rule]()

You probably want to know when isEnabled in Rule class changes. Right now it is not happening. For that to ObservableObject must comform the Rule class.

Keeping that in mind, if you change your code to:

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)
    }
  }
}

It will notice when new element is added to rules array, and also will notice when isEnabled changes. This also solves your problem with crashing.



来源:https://stackoverflow.com/questions/59335797/swiftui-foreach-using-array-index-crashes-when-rows-are-deleted

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!