Search code examples
swiftswiftuiviewobservedobject

SwiftUI: View not updating when Observed object changes from another file


I have looked at a few similar issues, but none were close enough to my state to work. I have 3 files:

  1. ContentView.swift
  2. VariableSetupModel.swift
  3. ContentNavView.swift

Previously the code in all 3 were in a single file (ContentView). I am now working on splitting it up to make the code cleaner. I am a fairly novice iOS developer. I moved all of my variables to the VariableSetupModel and got that working fine. I reference values in that file, they update, and return to the ContentView to update the views.

Now I am working on splitting my views on the same page into multiple files, starting with the nav. The view shows up, reading values from the VariableSetupModel correctly when the page first opens. But when the value changes from a button in ContentView, it does not update the title on the view in ContentNavView. Below is a simplified version of the code I have.

ContentView.swift

struct ContentView: View {
    //Pull in variables from "VariableSetupModel"
    @ObservedObject var viewModel = VariableSetupModel()
    
    @State var counter:Int = 0

    
   var body: some View {
        //Main Views
        VStack (alignment: .leading) {
            
        // MARK: Climbs-Title
            ContentNavView()
        }
    }

    Button(action: {
        if viewModel.toggleListGraphs == "graph" {
            viewModel.toggleListGraphs = "list"
        }else{
            viewModel.toggleListGraphs = "graph"
        }        
    }) {
       Image(systemName: "list.bullet.below.rectangle")
           .font(.title)
       }
}

VariableSetupModel.swift

final class VariableSetupModel: ObservableObject {
    static let shared = VariableSetupModel()

    //Visuals
    @Published var toggleListGraphs = "graph"
}

ContentNavView.swift

struct ContentNavView: View {
    //Pull in variables from "VariableSetupModel"
    @ObservedObject var viewModel = VariableSetupModel()
    //var viewModel = VariableSetupModel.shared
    
    var body: some View {
        HStack{
            if viewModel.toggleListGraphs == "graph"{
                Text("All Sends")
                .font(.title)
                .padding(.horizontal)
            }else{
                Text("All Climbs")
                .font(.title)
                .padding(.horizontal)
            }
            
            Spacer()
            
            Button(action: {
                viewModel.showSettingsModal = true
            }) {
                Image("logoIcon")
                    .renderingMode(.original)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 35, height: 35)
            }.padding().foregroundColor(Color("whiteblack"))

        }.sheet(isPresented: $viewModel.showSettingsModal, onDismiss: {
            //print(viewModel.showSettingsModal)
        }) {
            SettingsView()
        }
    }
}

Solution

  • There are many ways to have the viewModel changes reflected in different Views, the following is an example, where the main idea is to have one source of truth that you use in all the different Views:

    declare @StateObject var viewModel = VariableSetupModel() in ContentView and @EnvironmentObject var viewModel: VariableSetupModel in ContentNavView.

    Pass viewModel from ContentView to ContentNavView using .environmentObject(viewModel), that is, add this to ContentView, eg: VStack (alignment: .leading) {...}.environmentObject(viewModel)

    Note, there is no need for VariableSetupModel to be a singleton, remove the static let shared = VariableSetupModel().

    Note also, to have the viewModel available in SettingsView() use, SettingsView().environmentObject(viewModel)

    Another approach is to pass a ObservedObject from one view to another. For example:

    declare @StateObject var viewModel = VariableSetupModel() in ContentView and
    @ObservedObject var viewModel: VariableSetupModel in ContentNavView.

    Pass viewModel from ContentView to ContentNavView using ContentNavView(viewModel: viewModel).