joels
joels

Reputation: 7711

Why doesn't this binding work when it's in a subview on macOS?

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.

https://youtu.be/7Kvh2w8z__4

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

Answers (1)

joels
joels

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

Related Questions