-1

I have been having problems with updating a published variable in my model, so I tried to replicate the problem with a very basic and simple set of files/codes. So basically in NavLink view, there is a navigation link, which when clicked, it updates the published variable in ListRepository model by giving it a string value of "yes", prints it to the console then navigates to its destination which is called ContentView view. The problem is in ContentView, I tried to print the data contained in the published variable called selectedFolderId hoping it will print "yes", but i noticed that instead of printing the value that was set in NavLink view, it instead printed the default value of "", which was not what was set in NavLink view. Please can anyone explain the reason for this behaviour and explain to me how it can fix this as i am very new in swift ui. That will mean alot.

Please find the supporting files below:

import SwiftUI

struct NavLink: View {

    @StateObject var listRepository = ListRepository()
    
    var body: some View {
        
        NavigationView{
            ScrollView {
                
                NavigationLink("Hello world", destination: ContentView(listRepository: listRepository))
                
                Text("Player 1")
                Text("Player 2")
                Text("Player 3")
            }
            .simultaneousGesture(TapGesture().onEnded{
                listRepository.selectedFolderId = "yes"
                listRepository.md()
            })
            .navigationTitle("Players")
        }
    }
}

struct NavLink_Previews: PreviewProvider {
    static var previews: some View {
        NavLink()
    }
}
import Foundation


class ListRepository: ObservableObject {
    
    @Published var selectedFolderId = ""
    
    func md(){
        print("=====")
        print(self.selectedFolderId)
        print("======")
    }
}
import SwiftUI

struct ContentView: View {
    
    @ObservedObject var taskListVM = ShoppingListItemsViewModel()
    @ObservedObject var listRepository:ListRepository
    
    var body: some View {
        VStack{
            Text("content 1")
            Text("content 2")
            Text("content 3")
        }
        .onAppear{
            taskListVM.addTask()
            print("========")
            print(listRepository.selectedFolderId)
            print("========")
            
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
class ShoppingListItemsViewModel: ObservableObject {
    
    @Published var listRepository = ListRepository()
    
    @Published var taskCellViewModels = [ShoppingListItemCellViewModel]()
    
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        
        listRepository.$tasks
            .map { lists in
                lists.map { list in
                    ShoppingListItemCellViewModel(task: list)
                }
            }
            .assign(to: \.taskCellViewModels, on: self)
            .store(in: &cancellables)
    }
    
    func addTask() {
        listRepository.addTask(task)
    }
}
Emere
  • 45
  • 4

1 Answers1

-1

This is a common issue when you first deal with data flow in an app. The problem is straightforward. In your 'NavLink' view you are creating one version of ListRepository, and in ContentView you create a separate and different version of ListRepository. What you need to do is pass the ListRepository created in NavLink into ContentView when you call it. Here is one example as to how:

struct NavLink: View {

    @StateObject var listRepository = ListRepository() // Create as StateObject, not ObservedObject
    
    var body: some View {
        
        NavigationView{
            ScrollView {
                
                NavigationLink("Hello world", destination: ContentView(listRepository: listRepository)) // Pass it here
                
                Text("Player 1")
                Text("Player 2")
                Text("Player 3")
            }
            .simultaneousGesture(TapGesture().onEnded{
                listRepository.selectedFolderId = "yes"
                listRepository.md()
            })
            .navigationTitle("Players")
        }
    }
}

struct ContentView: View {

    @ObservedObject var listRepository: ListRepository // Do not create it here, just receive it
    
    var body: some View {
        VStack{
            Text("content 1")
            Text("content 2")
            Text("content 3")
        }
        .onAppear{
            print("========")
            print(listRepository.selectedFolderId)
            print("========")
            
        }
    }
}

You should also notice that I created ListRepository as a StateObject. The view that originally creates an ObservableObject must create it as a StateObject or you can get undesirable side effects.

Yrb
  • 8,103
  • 2
  • 14
  • 44
  • Thanks alot, this makes alot of sense now. The only issue now is, this instance of `ListRepository class` which has been passed from `NavLink view` to `ContentView`, is it possible to pass that instance into another model `ShoppingListItemsViewModel`, as i wish to use a function in it but it created its own instance of `ListRepository`. I have edited the code above to factor in this question, basically i am making an instance of `ShoppingListItemsViewModel`, in order to use a method in it, but in that class - `ShoppingListItemsViewModel`, creates another instance of `ListRepository` – Emere Sep 18 '22 at 18:35
  • 1
    If you haven't already, I would go through [Apple's SwiftUI Tutorial](https://developer.apple.com/tutorials/swiftui). These are pretty basic data flow questions that you really need a solid foundation for. The answer is yes, but it depends upon how and where you create `ShoppingListItemsViewModel` as to how to get to it to use the function. You could also consider making the function static and passing in the values that you need to use in the function. But definitely go through Apple's tutorial. – Yrb Sep 18 '22 at 18:41