@EnvironmentObject not updating Home View when @Published var is changed by a function

***UPDATED***

See poya's correct answer below-- the problem was that the button was calling a new instance of UserData instead of the environment object that I intended to reference. Once the button referenced the correct object, it worked!




Using XCode 11.4, Swift 5.0


After exploring dozens of different ways to try and solve this problem and spending almost a week trying to solve it, I'd like to seek insight as to what could be going wrong.


To learn SwiftUI I am building a single-view app that uses CoreMotion gyroscope to sense when the iPhone is held in a specific 3D orientation; when iPhone is held in this orientation, I add +1 to a score count that is saved to user defaults. The score count & user default values are confirmed to print the correct integer (in the debug log) every time the iPhone is held in the right orientation, but the Text view on the main screen will not auto-update itself -- I have only been able to get the Text view manually updated when I set *score count = user default* as a Button action.


My understanding is that using @EnvironmentObject is the best way to auto-update UI views when a @Published variable changes.

Please see my current attempt below:


HomeView

(single page view of app)

import Foundation
import SwiftUI
import Combine

struct HomeView: View {
    //sets up the userData class to share data with the HomeView
    @EnvironmentObject var userData: UserData
    
    var body: some View {
            
            //***score counter, calling the @Published variable scoreCount from userData
            VStack {
                Text("\(userData.scoreCount)")
                Text("Score Count")

                //***This button should start gyro, and every time iPhone is in specific orientation,
                //the score count should increase by +1 and the new score should auto-update in
                //the above text
                Button(action:{
        
            UserData().manageMotionCapture()
                }){
                     Text("START MOTION CAPTURE")
                     }

                //My test button that updates the above userData.scoreCount if it is pressed
                Button(action:{
                     self.userData.correctionCount = defaults.integer(forKey: "CorrCount")
                     print("\(self.userData.correctionCount)")
                }){
                     Text("SHOW SCORE")
                     }
             }
      }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView().environmentObject(UserData())
    }
}


UserData ObservableObject class

(contains published var scoreCount and the CoreMotion gyro functions that calculate when to add +1 to scoreCount)

import Foundation
import SwiftUI
import CoreMotion
import Combine

// sets up the user defaults for app storage of the score count
let defaults = UserDefaults.standard
//Sets up the gyroscope for use in the functions below
let motion = CMMotionManager()
var timer = Timer()

//Contains the score Count and the functions to run the user's gyroscope when called
final class UserData: ObservableObject  {

    @Published var scoreCount = defaults.integer(forKey: "scoreCount")

    //When button is pressed on HomeView, this function is called and
    //stops all previous gyros/timers and starts a new one. Gyro effectively restarts with every button press
    func manageMotionCapture() {
     /// sets up motion capture logic based on Apple's template //
     /// logic adds +1 based on orientation ///
     }

(Puts the EnvironmentObject into the view so it is accessible to app)

import Foundation
import UIKit
import SwiftUI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
       
        // Use a UIHostingController as window root view controller
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            
            //Sets the HomeView as the root view
            window.rootViewController = UIHostingController(rootView: HomeView().environmentObject(UserData()))
            self.window = window
            window.makeKeyAndVisible()
        }
    }


As mentioned before, the scoreCount variable and user defaults are both being incremented by +1, correctly shown by the print statements in the log when gyroscope is actively running. However, the scoreCount changes are not automatically propagated to the

HomeView's Text("\(userData.scoreCount)") It seems as though something simple is missing but after many days I haven't been able to sort it out?


Thank you very much for any insight on how to fix this!!

Answered by poya in 414432022

It looks like your button that starts the motion capture does so on a new separate instance of UserData,

rather than on the EnvironmentObject that you have bound your UI elements to.

Button(action:{ 
    UserData().manageMotionCapture()
})



So you might want to try changing that action to the following, and hopefully that will get your UI updating correctly.

Button(action:{ 
    self.userData.manageMotionCapture()
})
Accepted Answer

It looks like your button that starts the motion capture does so on a new separate instance of UserData,

rather than on the EnvironmentObject that you have bound your UI elements to.

Button(action:{ 
    UserData().manageMotionCapture()
})



So you might want to try changing that action to the following, and hopefully that will get your UI updating correctly.

Button(action:{ 
    self.userData.manageMotionCapture()
})

Dear poya, can't thank you enough!!! This works perfectly, don't know how I passed up this minor mistake so many times but I'm so thankful you pointed it out. Appreciate the help!

@EnvironmentObject not updating Home View when @Published var is changed by a function
 
 
Q