3

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

1 Answer 1

3

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 be 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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.