-1

Here is a simple representation of what I'm trying to do. I need to have a view updated when values change in a class stored inside a published var. This ContentView should be updated when Items is updated in the ItemManager stored in the state of the ContentViewViewModel.

The ContentView with the ForEach well display the Item stored in the array, but if I store the array of Item in the ContentViewState the view is updated correctly so why can't I move the data and logic attach to it in a subclass?

struct ContentView: View {
    @ObservedObject private var viewModel = ContentViewViewModel()
    
    var body: some View {
        VStack {
            ForEach(viewModel.state.itemManager.items, id: \.id) { item in
                Text(item.value)
            }
            
            Button("Add Item") {
                viewModel.triggerAddButton()
            }
        }
    }
}

class ContentViewViewModel: ObservableObject {
    
    @Published var state = ContentViewState()
    
    func triggerAddButton() {
        state.itemManager.addItem()
    }
}

struct ContentViewState {
    let itemManager = ItemManager()
}

class ItemManager: ObservableObject {
    @Published var items: [Item] = [Item("Item 1"), Item("Item 2")]
    
    func addItem() {
        items.append(.init("NewItem"))
    }
}

struct Item: Identifiable {
    var id: UUID
    var value: String
    
    init(_ value: String) {
        self.value = value
        self.id = UUID()
    }
}
Jonathan
  • 55
  • 6
  • There is too much indirection here for no particular good reason. I would simplify this by removing MyState and and putting a Publisher of [String] directly on your view model, then feed that the Strings from whatever source of truth you have. ItemManager is really just an implementation detail. – Aaron Jun 08 '23 at 16:45
  • All `ItemManager` need to be wrapped with `@ObservedObject` or switch it to a `struct` – lorem ipsum Jun 08 '23 at 16:48
  • Also have a look at this link, it gives you some good examples of how to manage data in your app using `ObservableObject`: [Managing model data in your app](https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app). In essence, you have your code structure back to front. Also note that **nesting** ObservedObjects is not a good idea. – workingdog support Ukraine Jun 09 '23 at 00:02

1 Answers1

0

ForEach is not a for loop, you can't use indices you need to make a struct that is Identifiable with an id, eg

struct Item: Identifiable {
    let id = UUID()
    var text = ""
}

StateObject is for when you want to do something asynchronous like networking or use a Combine pipeline, in your case State is enough:

struct ContentView: View {

    @State private var items: [Item] = [Item(text: "Item 1")]
    
    func addItem() {
        items.append("NewItem")
    }

Or

struct Content {
    var items: [Item] = [Item(text: "Item 1")]
    
    mutating func addItem() {
        items.append("NewItem")
    }
}

struct ContentView: View {

    @State private var content = Content()
malhal
  • 26,330
  • 7
  • 115
  • 133
  • You're right my exemple was confusing I've updated it, actually the ItemManager could be much complex with networking, but the main question is why does the view doesn't update if I move the `[Item]` from the `State` to a subclass I store in the `State`? – Jonathan Jun 09 '23 at 08:45
  • You need to use structs instead of classes in Swift – malhal Jun 09 '23 at 20:15