4

I don't manage to trigger the onReceive method in a SwiftUI View whenever a variable from ObservedObject changes.

I tried two methods: using @Publish and using PassthroughSubject<>

Here is the ViewModel

class MenuViewModel: ObservableObject {

@Published var selectedItems = Set<UUID>()
@Published var currentFocusItem: UUID?

// Output
let newItemOnFocus = PassthroughSubject<(UUID?), Never>()

// This function gets called good :)
func tapOnMenuItem(_ item: MenuItem) {
    if selectedItems.contains(item.id) {
        //These changes should trigger the onReceive?
        currentFocusItem = item.id  
        newItemOnFocus.send(item.id)
    } else {
        selectedItems.insert(item.id)
        currentFocusItem = nil
        newItemOnFocus.send(nil)
    }
}
}

Here is the View when trying to catch the changes in @Published var currentFocusItem

struct MenuView: View {

    @ObservedObject private var viewModel: MenuViewModel
    @State var showPicker = false
    @State private var menu: Menu = Menu.mockMenu()


    init(viewModel: MenuViewModel = MenuViewModel()) {
        self.viewModel = viewModel
    }

    var body: some View {
        VStack {
            List(menu.items, selection: $viewModel.selectedItems) { item in
                MenuItemView(item: item)
            }

            Divider()
            getBottomView(showPicker: showPicker)
        }

        .navigationBarTitle("Title")
        .navigationBarItems(trailing: Button(action: closeModal) {
            Image(systemName: "xmark")
        })
        .onReceive(viewModel.$currentFocusItem, perform: { itemUUID in
            self.showPicker = itemUUID != nil // <-- This only gets called at launch time
        })           
    }
}

The View in the same way but trying to catch the PassthroughSubject<>

.onReceive(viewModel.newItemOnFocus, perform: { itemUUID in
            self.showPicker = itemUUID != nil // <-- This never gets called
        })

----------EDIT----------

Adding MenuItemView, although viewModel.tapOnMenuItem gets always called, so I am not sure if it's very relevant

MenuItemView is here:

struct MenuItemView: View {

    var item: MenuItem
    @ObservedObject private var viewModel: MenuViewModel = MenuViewModel()
    @State private var isSelected = false

    var body: some View {
        HStack(spacing: 24) {
            Text(isSelected ? " 1 " : item.icon)
                .font(.largeTitle)
                .foregroundColor(.blue)
                .bold()
            VStack(alignment: .leading, spacing: 12) {
                Text(item.name)
                    .bold()
                Text(item.description)
                    .font(.callout)
            }
            Spacer()
            Text("\(item.points)\npoints")
                .multilineTextAlignment(.center)
        }
        .padding()
        .onTapGesture {
            self.isSelected = true
            self.viewModel.tapOnMenuItem(self.item). // <-- Here tapOnMenuItem gets called
        }
    }

    func quantityText(isItemSelected: Bool) -> String {
        return isItemSelected ? "1" : item.icon
    }
}

What am I doing wrong?

2
  • Please show us MenuItem and where tapOnMenuItem is called. Commented Feb 27, 2020 at 18:49
  • @Asperi done! it gets called correctly always so I thought it wasn't important Commented Feb 27, 2020 at 19:38

1 Answer 1

7

Well, here it is - your MenuView and MenuItemView use different instances of view model

1)

struct MenuView: View {

    @ObservedObject private var viewModel: MenuViewModel
    @State var showPicker = false
    @State private var menu: Menu = Menu.mockMenu()


    init(viewModel: MenuViewModel = MenuViewModel()) { // 1st one created

2)

struct MenuItemView: View {

    var item: MenuItem
    @ObservedObject private var viewModel: MenuViewModel = MenuViewModel() // 2nd one

thus, you modify one instance, but subscribe to changes in another one. That's it.

Solution: pass view model via .environmentObject or via argument from MenuView to MenuItemView.

Sign up to request clarification or add additional context in comments.

1 Comment

Omg! what a stupidity I did 🤦‍♀️! Thanks a lot you are totally right! I passed it as a parameter and It works now!

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.