Reputation: 7711
I have a couple layers of ObservableObjects and Published properties. When I use them directly in a view, they seem to work as expected. However, when I try to move the list into it's own type, the bindings in the parent view don't seem to work.
For example, why the ModelList is enabled, when you select rows, the Button does not toggle between enabled and disabled. However, if you comment that out and enable the List.init lines, then when selecting and unselecting rows, the Button correctly enables and disables.
This works
View
List(selection: viewModel.dataStore.selection)
This does not
View
ModelList(dataStore: viewModel.dataStore)
List(selection: dataStore.selection)
Full code example
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
// Using the the dataStore, the button bind works
// List.init(viewModel.dataStore.models, id: \.id, selection: $viewModel.dataStore.selection) {
// Text("Name: \($0.name)")
// }
// Using the dataStore in the subview, the button binding doesn't work
ModelList(dataStore: viewModel.dataStore)
Button(action: {
print("Delete")
}, label: {
Image(systemName: "minus")
})
.disabled($viewModel.dataStore.selection.wrappedValue.count == 0)
Text("Selection \($viewModel.dataStore.selection.wrappedValue.count)")
}
}
}
struct ModelList: View {
@ObservedObject public var dataStore: DataStore
var body: some View {
List.init(dataStore.models, id: \.id, selection: $dataStore.selection) {
Text("Name: \($0.name)")
}
}
}
class ViewModel: ObservableObject {
@Published var dataStore: DataStore = DataStore()
}
class DataStore: ObservableObject {
@Published public var selection = Set<Int>()
@Published public var models = [Model(id: 1, name: "First")]
}
struct Model: Identifiable {
let id: Int
let name: String
}
@main
struct LayersApp: App {
var body: some Scene {
WindowGroup {
ContentView(viewModel: ViewModel())
}
}
}
Upvotes: 0
Views: 90
Reputation: 7711
The subview should accept a Binding, not another ObservedObject.
@ObservedObject public var dataStore: DataStore
should be @Binding public var dataStore: DataStore
Now when using the subview, pass in the binding ModelList(dataStore: $viewModel.dataStore)
Complete working example:
struct ContentView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
ModelList(dataStore: $viewModel.dataStore)
Button(action: {
print("Delete \(viewModel.dataStore.selection)")
}, label: {
Image(systemName: "minus")
})
.disabled($viewModel.dataStore.selection.wrappedValue.count == 0)
Text("Selection \($viewModel.dataStore.selection.wrappedValue.count)")
}
}
}
struct ModelList: View {
@Binding public var dataStore: DataStore
var body: some View {
List.init(dataStore.models,
id: \.id,
selection: $dataStore.selection) {
Text("Name: \($0.name)")
}
}
}
class ViewModel: ObservableObject {
@Published var dataStore: DataStore = DataStore()
init() {
print("ViewModel")
}
}
class DataStore: ObservableObject {
@Published public var selection = Set<Int>()
@Published public var models = [Model(id: 1, name: "First")]
init() {
print("DataStore")
}
}
struct Model: Identifiable, Equatable {
let id: Int
let name: String
}
@main
struct LayersApp: App {
var body: some Scene {
WindowGroup {
ContentView(viewModel: ViewModel())
}
}
}
Upvotes: 1