swiftmvvmswift-class

Value of a var in @Obersvable class only updates in one view, but not the other


I have a Companion WatchOS App with a WatchViewModel, that currently holds all my variables that get send from the App. I have to views, each should display another variable. In one view (ActivityDistributionView) everything works perfectly fine, but in the other one (LeaderboardView) the value is the init value and is not getting changed. Actually no value gets updated in the LeaderboardView, but in the ActivityDistributionView, all values work fine.

What I found out is, that if I comment out the declaration of the ViewModel in ActivityDistribution, then my code Works. Does this mean I can only declare a working instance of the class once?

ContentView body:
var body: some View {
        TabView{
            ScoreTabView(dailyScore: viewModel.scores["score"] ?? 0, postureScore: viewModel.scores["posture"] ?? 0, movementScore: viewModel.scores["movement"] ?? 0)
            
            NavigationStack{
                List{
                    Section("Last message received:"){
                        Text("\(formatDate(date: viewModel.lastMessageTime))").font(.body)
                        //Text("\(viewModel.lastMessage.keys.sorted().formatted())").padding()
                    }
                    NavigationLink("Leaderboard"){
                        LeaderboardViews()
                    }
                    NavigationLink("Activity Distribution"){
                        ActivityDistributionView()
                    }
                }
            }
        }
    }


struct LeaderboardViews: View {
    @State private var viewModel: WatchViewModel = WatchViewModel()
    var body: some View {
        Text("\(viewModel.leaderboardMessage)")
            
    }
}

struct ActivityDistributionView: View {
    @State private var viewModel: WatchViewModel = WatchViewModel()
    @State private var vibrates = false
    let activities = ["sitting", "walking", "standing", "onDesk", "driving", "lying"]

    
    var body: some View {
        NavigationStack{
            ScrollView{
                Text("\(viewModel.leaderboardMessage)")
            }
            .navigationTitle("Acitivity Distribution")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}

    @Observable
    class WatchViewModel: NSObject{
        var session: WCSession
        
        var activityDistribution: [String: Any] = [:]
        var scores: [String: Double] = [:]
        var leaderboardMessage: LeaderboardMessage = LeaderboardMessage(method: "InitExampleLeaderboardMessage", payload: [:])

        
        //more code
        
    }


    //Sends Data from WatchOS to AppDelegate (to later push to Flutter)
extension WatchViewModel: WCSessionDelegate {
    //code that works
case .LeaderboardMessage:
    self.lastMessage = message["payload"] as! [String : Any]
    
        //message gets converted into JSON
        let messageAsJSON = self.encodeJSONData(encode: message)
        //message gets decoded into the proper struct
        let decodedMessage = self.decodeJSONData(decode: messageAsJSON!) ?? LeaderboardMessage(method: "ExampleLeaderboardData", payload: ["Date" : [ExerciseDataWrapper(name: "Nobody", data: ExerciseData(duration: ExerciseData.Duration(displayString: "/", value: 0), exerciseScore: ExerciseData.ExerciseScore(displayString: "/", value: 0), movement: ExerciseData.Movement(displayString: "/", value: 0), posture: ExerciseData.Posture(displayString: "/", value: 0), postureAboveThresholdDuration: ExerciseData.PostureAboveThresholdDuration(displayString: "/", value: 0), score: ExerciseData.Score(displayString: "/", value: 0)))]])
        //leaderboardMessage gets updated to the last send leaderboardMessage
    self.leaderboardMessage = decodedMessage
    //assigning works but not in the LeaderboardsView()
}


I tried changing the name and the location of the NavLink or trying to pass the viewModel value from my ContentView through to my LeaderboardView but it didn't work.


Solution

  • You should have one instance on the ContentView and pass it down from there. For some reason swift decided to use the ViewModel-Instance declared in the ActivityView rather than the one in my ContentView, when I tried passing it down from the ContentView to the other views, and they never updated.

    So the correct code would be to have the viewModel in my ContentView and pass it down from there to my other views.

    struct LeaderboardViews: View {
    let leaderboardMessage: LeaderboardMessage
    var body: some View {
        Text("\(leaderboardMessage.method)")
           
    }
    

    }

    struct ActivityDistributionView: View {
    @State private var vibrates = false
    let activities = ["sitting", "walking", "standing", "onDesk", "driving", "lying"]
    let activityDistribution: [String: Double]
    
    
    var body: some View {
        NavigationStack{
            /*ForEach(viewModel.leaderboardData.keys.sorted(), id: \.self) { day in
                Text("Day: \(viewModel.leaderboardData[day] ?? "No Date")")
            }*/
            ScrollView{
                //code
            }
            .navigationTitle("Acitivity Distribution")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
    

    }

    struct ContentView: View {
    @State private var viewModel: WatchViewModel = WatchViewModel()
    @State private var vibrates = false
    let activities = ["sitting", "walking", "standing", "onDesk"]
    var body: some View {
        TabView{
            ScoreTabView(dailyScore: viewModel.scores["score"] ?? 0, postureScore: viewModel.scores["posture"] ?? 0, movementScore: viewModel.scores["movement"] ?? 0)
            
            NavigationStack{
                List{
                    Section("Last message received:"){
                        Text("\(formatDate(date: viewModel.lastMessageTime))").font(.body)
                        //Text("\(viewModel.lastMessage.keys.sorted().formatted())").padding()
                    }
                    NavigationLink("Leaderboard"){
                        LeaderboardViews(leaderboardMessage: viewModel.leaderboardMessage)
                    }
                    NavigationLink("Activity Distribution"){
                        ActivityDistributionView( activityDistribution: viewModel.activityDistribution as! [String: Double])
                    }
                }
            }
        }
    }
    

    }