***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!!
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()
})