4

I'm unsure why my view isn't getting updated when I have a view model nested inside another. My understanding was that a @Published property in the child view model would trigger a change in the parent viewModel, causing that to push changes to the UI.

This is the child view model:

class FilterViewModel : ObservableObject, Identifiable {

    var id = UUID().uuidString
    var name  = ""
    var backgroundColour = ""
    @Published var selected = false

    private var cancellables = Set<AnyCancellable>()

    init(name: String){
        self.name = name
    
        $selected.map { _ in
            self.selected ? "Orange" : "LightGray"
        }
        .assign(to: \.backgroundColour, on: self)
        .store(in: &cancellables)
     }

    func changeSelected() {
    
        self.selected = !self.selected
    }
}

The following works as expected, on clicking the button the background colour is changed.

struct ContentView: View {

    @ObservedObject var filterVM = FilterViewModel(name: "A")


    Button(action: { filterVM.changeSelected()}, label: {
        Text(filterVM.name)
            .background(Color(filterVM.backgroundColour))
    })
}

However, I want to have an array of filter view models so tried:

class FilterListViewModel: ObservableObject {

    @Published var filtersVMS = [FilterViewModel]()

    init(){
        filtersVMS = [
            FilterViewModel(name: "A"),
            FilterViewModel(name: "B"),
            FilterViewModel(name: "C"),
            FilterViewModel(name: "D")
        ]
    }
}

However, the following view is not updated when clicking the button

struct ContentView: View {

    @ObservedObject var filterListVM = FilterListViewModel()

    Button(action: { filterListVM[0].changeSelected()}, label: {
        Text(filterListVM[0].name)
            .background(Color(filterListVM[0].backgroundColour))
    })
}

2 Answers 2

5

Alternate solution is to use separated view for your sub-model:

struct FilterView: View {
    @ObservedObject var filterVM: FilterViewModel

    var body: some View {
      Button(action: { filterVM.changeSelected()}, label: {
        Text(filterVM.name)
            .background(Color(filterVM.backgroundColour))
      })
    }
}

so in parent view now we can just use it as

struct ContentView: View {

    @ObservedObject var filterListVM = FilterListViewModel()

    // ...

    FilterView(filterVM: filterListVM[0])
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, this worked perfectly. Do you know why this works rather than referencing the child VM through its parent?
ObservedObject observes only one level published values changing, in your case reference to child is not changed when internal child properties changed.
0

The most simplest way is to define your FilterViewModel as struct. Hence, it is a value type. When a value changes, the struct changes. Then your ListViewModel triggers a change.

struct FilterViewModel : Identifiable {

    var id = UUID().uuidString
    var name  = ""
    var backgroundColour = ""
    var selected = false

    private var cancellables = Set<AnyCancellable>()

    init(name: String){
        self.name = name
    
        $selected.map { _ in
            self.selected ? "Orange" : "LightGray"
        }
        .assign(to: \.backgroundColour, on: self)
        .store(in: &cancellables)
     }

    mutating func changeSelected() {
    
        self.selected = !self.selected
    }
}

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.