1

Yesterday, I've ask because I have a problem with my timer in background, I've found a solution to make it works but now, I got a problem when my application enter in background.

Run in background:

backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
        UIApplication.shared.endBackgroundTask(self.backgroundTaskIdentifier!)
    })

This is my timer:

let interval = 0.01
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector:#selector(ViewController.updateTimer), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)

This is my updateTimer function:

func updateTimer () {
    var j = 0
    for _ in rows {
      if (rows[j]["Playing"] as! Bool == true ) {
            rows[j]["time"] = (rows[j]["time"] as! Double + interval) as AnyObject
            rows[j]["lastTime"] = (rows[j]["lastTime"] as! Double + interval) as AnyObject
      }
      if (rows[j]["lastTime"] as! Double > 60.0) {
            min[j] += 1
            rows[j]["lastTime"] = 0.00 as AnyObject
      }
      j += 1
    }
}

In my applicationDidEnterBackground method I call this function:

func backgroundTimer() {
    print("background") // This Print works fine
    timer.invalidate() // Never works
    interval = 1.00 // Crash when interval change from 0.01 to 1.00
    timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector:#selector(ViewController.updateTimer), userInfo: nil, repeats: true)
}

And this is my output when my application enter in background:

enter image description here

The Double is interval.

In Info.plist I add : Application does not run in background : NO.

Let me know what i'm doing wrong?

EDIT:

Initialization of rows in viewDidLoad method

let i: [String : AnyObject] = ["time": time as AnyObject, "Playing": false as AnyObject, "lastTime": 0.00 as AnyObject, "lapNumber": 0 as AnyObject, "min": 0 as AnyObject]
rows.append(i as [String : AnyObject])
Community
  • 1
  • 1
pierreafranck
  • 445
  • 5
  • 19

3 Answers3

1

Your basic problem seems to be the use of AnyObject for things that are not really objects. It causes the as! Bool to fail.

Here's a playground snippet that gets back a stored Bool value by allowing simple values in the dictionary.

var rows: [[String : Any]] = []
let i: [String : Any] = ["time": time, "Playing": false, "lastTime": 0.00, "lapNumber": 0, "min": 0]
rows.append(i)

let playing = rows[0]["Playing"]
if let playing = playing as? Bool {
    print("Bool")
} else {
    print("Something else \(String(describing: playing))")
}
Phillip Mills
  • 30,888
  • 4
  • 42
  • 57
1

I found solution for this problem.

Using NSNotification.

override func viewWillAppear(_ animated: Bool) {
  NotificationCenter.default.addObserver(self, selector:#selector(ViewController.backgroundTimer), name:NSNotification.Name.UIApplicationDidEnterBackground, object: nil)

  NotificationCenter.default.addObserver(self, selector:#selector(ViewController.backToForeground), name:NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
}

func backgroundTimer(notification : NSNotification) {
    self.timer.invalidate()
    interval = 1.00
    timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector:#selector(ViewController.updateTimer), userInfo: nil, repeats: true)
    RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
}

func backToForeground(notification: NSNotification) {
    self.timer.invalidate()
    interval = 0.01
    timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector:#selector(ViewController.updateTimer), userInfo: nil, repeats: true)
    RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
}

Don't forgot to add the backgroundTaskIdentifier.

pierreafranck
  • 445
  • 5
  • 19
1

I suppose you are using the AppDelegate mainVC method that create a copy of your main. Thats why your timer are never invalidate in your ViewController. It is invalidate in your copy. Check out this answer: Pausing timer when app is in background state Swift