I'm trying to implement a UI for a classic parent-children (master-details, etc.) model. All works fine when I have a direct reference to the children in the view, but when I introduce a view-model for the children the UI stops updating when children are updated. I'm pretty sure that when I introduce the child view model I am creating a copy of the child struct and that is why the UI is not being updated. I suspect there is a common pattern I should be using but have not yet discovered.
I'm using Xcode 13.3 and so, I believe, Swift 5.5.
Here's code I'm using to try to solve the problem. This code does not use a child view model and the UI is correctly updated:
Working Code
Model
import Foundation
struct Parent {
    private(set) var children: Array<Child>
    private(set) var numberTimes: Int
    
    init(numberOfChildren: Int) {
        numberTimes = 0
        children = []
        for index in 0..<numberOfChildren {
            children.append(Child(value: index))
        }
        
        toggleRandom()
    }
    
    mutating func choose(child: Child) {
        if let chosenIndex = children.firstIndex(where: { $0.value == child.value }) {
            children[chosenIndex].toggleIsSelected()
        }
    }
    
    mutating func pressMe() {
        numberTimes += 1
    }
    
    mutating func toggleRandom() {
        children[Int.random(in: 0..<children.count)].toggleIsSelected()
    }
}
struct Child: Identifiable {
    let id: Int
    private(set) var value: Int
    private(set) var isSelected = false
    
    init(value: Int) {
        self.id = value
        self.value = value
    }
    
    mutating func toggleIsSelected() {
        isSelected.toggle()
    }
}
ViewModel with raw children
import SwiftUI
class ParentViewModel: ObservableObject {
    @Published var parent: Parent
    
    init(numberOfChildren: Int) {
        parent = Parent(numberOfChildren: numberOfChildren)
    }
    
    var children: Array<Child> {
        parent.children
    }
    
    var parentText: String {
        "'Press Me' pressed \(parent.numberTimes)"
    }
    
    func pressMe() {
        parent.pressMe()
    }
    
    func toggleRandom() {
        parent.toggleRandom()
    }
}
View with raw children
import SwiftUI
struct ContentView: View {
    @ObservedObject var parentViewModel: ParentViewModel
    
    var body: some View {
        VStack {
            Text(parentViewModel.parentText).padding()
            Button { parentViewModel.pressMe() }
                label: { Text("Press Me") }
            
            ForEach(parentViewModel.children, id: \.id) { child in
                Text(String(child.value))
                    .foregroundColor(child.isSelected ? .white : .black)
                    .background(child.isSelected ? .black : .white)
            }
            
            Button { parentViewModel.toggleRandom() }
                label: { Text("Toggle Random") }
        }
    }
}
Here's the code modified to use a view model for the child data elements. The model is the same as above, so only the view model and view code is different. This code continues to update the parent text correctly, but does not update the UI when child values change.
Not Working Code
ViewModel with child view model
import SwiftUI
class ParentViewModel: ObservableObject {
    @Published var parent: Parent
    private(set) var children: Array<ChildViewModel>
    
    init(numberOfChildren: Int) {
        parent = Parent(numberOfChildren: numberOfChildren)
        children = []
        for child in parent.children {
            children.append(ChildViewModel(child: child))
        }
    }
    
    var parentText: String {
        "'Press Me' pressed \(parent.numberTimes)"
    }
    
    func pressMe() {
        parent.pressMe()
    }
    
    func toggleRandom() {
        parent.toggleRandom()
    }
}
class ChildViewModel: ObservableObject, Identifiable {
    @Published var child: Child
    
    init(child: Child) {
        self.child = child
    }
    
    var value: Int {
        child.value
    }
    
    var isSelected: Bool {
        child.isSelected
    }
}
View with child view model
import SwiftUI
struct ContentView: View {
    @ObservedObject var parentViewModel: ParentViewModel
    
    var body: some View {
        VStack {
            Text(parentViewModel.parentText).padding()
            Button { parentViewModel.pressMe() }
                label: { Text("Press Me") }
            
            ForEach(parentViewModel.children, id: \.id) { child in
                ChildView(childViewModel: child)
            }
            
            Button { parentViewModel.toggleRandom() }
                label: { Text("Toggle Random") }
        }
    }
}
struct ChildView: View {
    @ObservedObject var childViewModel: ChildViewModel
    var body: some View {
        Text(String(childViewModel.value))
            .foregroundColor(childViewModel.isSelected ? .white : .black)
            .background(childViewModel.isSelected ? .black : .white)
    }
}
