-4

I have added an observer to my app that listens when the app goes into background mode. I did that through this command:

NotificationCenter.default.addObserver(self, selector: #selector(enterBackground), name: UIApplication.willResignActiveNotification, object: nil)

Now I want my selector to run a timer.scheduled in the background. My code Looks like:

@objc func enterBackground() {
    Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
        print(self.secondsCount)
        self.secondsCount -= 1
    }
}

But the timer doesn’t print my print commands every second. Just once.

Does somebody know how to do that? Warm greetings!

AND NO! THIS ISN'T A DUPLICATE I READ A LOT OF ANSWERS AND QUESTIONS BUT I DID'T GET ANSWER FOR ME!

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 3
    Yes, this is a duplicate. It has been asked and answered many, many times. If you don't want this closed as a duplicate, I'd suggest you include links to other questions and clarify how your question is really different. – Rob Nov 30 '21 at 18:27
  • E.g., https://stackoverflow.com/a/61048308/1271826. – Rob Nov 30 '21 at 18:29
  • 4
    BTW, just because you didn’t like the answers to all of those duplicate questions, doesn't mean that your question hasn’t been answered. Namely, you cannot keep apps running perpetually in the background, except for very narrow exceptions, (e.g., music app, VOIP app, navigation app, enterprise apps not in the app store, etc.). Apple imposes this constraint to avoid having apps that quickly crush the users’ batteries without good reason. – Rob Nov 30 '21 at 18:34
  • Perhaps you can explain _why_ you want to do this and we might be able to suggest workarounds (or, more likely, point you to one of the many answers that show the particular workaround). E.g. one approach for count-down timer, another if you want to track the user's location as they move around, etc. Depending upon what you're trying to do, Apple has a lot of nice, out-of-process, features to lend a “running in the background” je ne sais quoi, without actually keeping your app running. The best of both worlds. But we can't help you unless you tell us what this timer is trying to achieve. – Rob Nov 30 '21 at 18:38
  • Ignoring the “keep it going in the background” question, the whole notion of “decrement `secondsCount` with a `Timer`” is not a viable solution, anyway. With `Timer`, you are *not* guaranteed that it will fire when you think it will. Instead, we save the datetime to which we are counting down, and then the repeating timer just updates the UI to show how much time is left, not by decrementing a counter, but rather by comparing the current time to the target time. [User notifications](https://developer.apple.com/documentation/usernotifications/) can be scheduled to tell use when time is expired. – Rob Nov 30 '21 at 18:50
  • @Rob i have been working with timers for a long time. There is no other way to program a timer without timer.scheduled. And yes, the timer.schedueld may not be super accurate, but this value is only in the millisecond range. –  Nov 30 '21 at 19:10
  • @Rob and yes, we can track the enddate of the timer, but we always have to update our label with timer.schedueld –  Nov 30 '21 at 19:11
  • @RonRed but you don't need to update the UI while the app is in the background – Leo Dabus Nov 30 '21 at 19:28
  • @LeoDabus yes that´s true –  Nov 30 '21 at 19:34
  • 1
    “timer.scheduled may not be super accurate, but this value is only in the millisecond range.” … but if the runloop is temporarily blocked (gasp), you can lose one or more entire occurrences of the timer firing. And if the app is suspended, as you’ve seen, it stops entirely. The idea is to shift from a counter to a timer that merely updates the UI when the app is active. And then you can let the app suspend in the course of its natural lifecycle rather than fighting it. Make it *look* like it’s running in the background, but really let it suspend and don’t kill the users battery. – Rob Nov 30 '21 at 20:48
  • @Rob I have now documented the date when the user leaves the app and then subtract this time with date.now when the user goes back into the app, this value is then my remaining seconds. Is it correct that way? –  Nov 30 '21 at 20:53
  • @RonRed you should use the same method for tracking elapsed time regardless of whether or not the user leaves the app. The problems with a timer missing timer fires will happen while your app is running. Your timer should just trigger updating the UI, and you should **always** use `current_time - start_time` to calculate the total elapsed time. – Duncan C Dec 01 '21 at 18:28
  • Does this answer your question? [Run Background Timer Swift](https://stackoverflow.com/questions/55236760/run-background-timer-swift) – MFerguson Dec 03 '21 at 18:09
  • @Rob, I am building a doctor patient app, which needs to send data to doctor after every 2 mins, no matter where the app is , foreground or background. And as you said, you can't run timer in background. so what do you suggest? – Abu Bäkr Jan 22 '22 at 14:44
  • @AbuBäkr - There are tons of narrow/specific carve-outs for [background execution](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes/) none of which are likely to be suitable for your scenario, but check them out. But you can't just get it every 2 minutes just because “I want to”, because it is, generally, a horrendous design (crushes batteries, uses up cellular data plans, etc.) that Apple simply prohibits. But, don't ask new questions in comments. Post your own question. But research first and share your findings, or else you'd get heavily down-voted. – Rob Jan 22 '22 at 19:00
  • 1
    @Rob, thanks a lot sir! Will do surely. – Abu Bäkr Jan 22 '22 at 20:10

1 Answers1

1

The short answer is that you can't have a timer running while your app is in the background. Your app gets "suspended" (stops getting CPU time) almost immediately after the user switches to another app. Apple does this both to conserve battery and to make the front-most app more responsive. (When you are being told that your app is about to be suspended, you can ask for additional background time, but that extra time was limited to 3 minutes last time I tried it. Rob reports below that in recent versions of iOS you only get about 30 seconds of extra time if you ask for it.) Only a limited number of app types are allowed to run in the background for more than a few seconds (e.g. music players, turn-by-turn navigation apps, VoIP apps.)

If you need to track the amount of time elapsed since some event, you should save the Date at the time the event occurs. While your app is in the foreground, run a timer and when it goes off, subtract the current Date from the saved Date to get elapsed time.

When you get notified that you are going to the background, stop your timer.

When you return to the foreground, restart your timer so that it recalculates the amount of time elapsed.

P.S. This is a duplicate. The answer to this and all other "how can I run a timer while I'm in the background" questions is "You can't." I provided an individual answer rather than just closing it as a duplicate in order to explain why you can't solve your problem the way you are attempting to

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • @rob I edited my answer to make it clearer that the norm is a very short time, and you only get a few **minutes** if you ask for it. – Duncan C Dec 01 '21 at 18:17
  • 1
    @Rob gotcha. I'm glad I qualified my edit with "last time I tried it." I edited my answer again based on your new information. Thanks for keeping me current. – Duncan C Dec 01 '21 at 19:13