SwiftUI: Run Timer in background and Control Center

Viewed 46

I have the following code in my ProjectApp: App

                   .onChange(of: phase) { newValue in
                        if model.isStarted {
                            if newValue == .background {
                                lastActiveTimeStamp = Date()
                            }

                            if newValue == .active {
                                let diff = Date().timeIntervalSince(lastActiveTimeStamp)
                                lastActiveTimeStamp = Date()
                                model.totalSeconds += Int(diff)
                            }
                        }
                    }

This seems to be working well when I close the app (so it's in the background). However, when I swipe down the control center (or whatever it's called, where you can adjust brightness, volume etc) it starts adding random values. This happens because the app doesn't go into background phase, but rather becomes inactive. And then, when it's active again it adds the new diff. I tried changing newValue == .background to newValue != .active but that didn't help either.

How do I actually make my timer work correctly in all states? Thanks!

1 Answers

The short answer is "You don't." When your app gets suspended, your timers stop running. Further, once your app is suspended/inactive, it can be killed at any point thereafter without any waring. At the point your timers are dead. Everything about your app is dead, including instance variable values.

You are supposed to save app state when you are told you are about to be made inactive. For timers, you need to decide how to represent the timer state and save data about that to a file, then use that saved data to restore your state.

You can save the Date() when you are told you are about to be suspended, and do math on the Date when your app returns to the foreground/is relaunched to figure out how much time has passed. You can also do math to figure out how much time is left on your timer when you are about to be suspended, and use that to create a new timer with the same amount of time remaining. The details depend on how you are using the timer in your app.

In your case, it looks like what you would want to save lastActiveTimeStamp. But what happens if your app is killed after being suspended and the user doesn't relaunch it for a week? Does it still make sense to calculate model.totalSeconds from the number of seconds since lastActiveTimeStamp? That might be a very large number.

Related