1

There are several useful questions on this topic, but all I've found either use deprecated syntax from earlier betas (e.g., BindableObject) or do not require a binding be passed (e.g., to a Toggle).

I want to create a list of Toggles that are bound to elements in an array. I've tried many methods, but the syntax is never corrrect. The version below most closely matches the answers to an existing question.

struct Item: Identifiable {
    var id: String { self.name }
    var name: String
    var enabled: Bool
}

final class ItemSet: ObservableObject {
    @Published var items: [Item]

    init() {
        items = [
            Item(name: "aaa", enabled: true),
            Item(name: "bbb", enabled: false),
            Item(name: "ccc", enabled: true)
        ]
    }
}

var myItems = ItemSet()

struct ContentView: View {
    @ObservedObject var items: ItemSet

    var body: some View {
        List {
            ForEach(items.items) { item in
                Toggle(item.name, isOn: $item.enabled)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(items: myItems)
    }
}

The compiler error I get in Xcode 11.1 is:

Use of unresolved identifier '$item'

on the line where Toggle is defined.

I was under the impression that each Item would itself need to be an ObservableObject with a @Published var enabled: Bool parameter, but I have not been able to get that working either, and all stackoverflow answers seems to say avoid making Item itself ObservableObject.

Any help would be much appreciated.

drootang
  • 2,351
  • 1
  • 20
  • 30
  • I believe the issue here is using ForEach. Because `item` is a struct, it's a copy of that struct? The Landmarks example from Apple uses an index directly into the `items.items` array... likely for this reason. – drootang Oct 23 '19 at 21:35

1 Answers1

4

You are confusing the property wrapper for items (which is a current value subject) with the binding parameter that a Toggle expects. See corrected implementation with binding below:

import SwiftUI
import Combine

struct Item: Identifiable {
  var isEnabled: Binding<Bool>
  var id: String { self.name }
  var name: String
  init(name: String, enabled enabledValue: Bool) {
    self.name = name
    let enabled = CurrentValueSubject<Bool, Never>(enabledValue)
    isEnabled = Binding<Bool>(
      get: { enabled.value },
      set: { enabled.value = $0}
    )
  }
}

final class ItemSet: ObservableObject {
  @Published var items: [Item]

  init() {
    items = [
      Item(name: "aaa", enabled: true),
      Item(name: "bbb", enabled: false),
      Item(name: "ccc", enabled: true)
    ]
  }
}

var myItems = ItemSet()

struct ContentView: View {
  @ObservedObject var items: ItemSet

  var body: some View {
    List {
      ForEach(items.items) { item in
        Toggle(isOn: item.isEnabled, label: { Text (item.name) })
      }
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView(items: myItems)
  }
}
Josh Homann
  • 15,933
  • 3
  • 30
  • 33