6

So I am building an simple iOS app using Swift. I need to kill my NSTimer after the app entering background, and create a new one after the app is active again.

Initially my solution is to create a timer of NSTimer class in ViewDidLoad() in the main controller file. And this causes a bug in my app.

I guess I need to kill the timer by using applicationDidEnterBackground() in AppDelegate.swift. But I am not exactly sure about how to do it. Should I create the timer class in AppDelegate.swift or in the main controller? I don't know how Swift files share classes.

I have searched online for solutions, but those posts are all too old, the solutions are written in Objective-C.

I am an absolutely beginner. So I hope someone can explain it in Swift.

Here is the code in my main controller TodoTableViewController.swift:

import UIKit
import CoreData
class TodoTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {....
...
...
override func viewDidLoad() {
    super.viewDidLoad()
    var fetchRequest = NSFetchRequest(entityName: "Todo")
    let sortDescriptor = NSSortDescriptor(key: "dayleft", ascending: true)
    fetchRequest.sortDescriptors = [sortDescriptor]
    if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext {
        fetchResultController = NSFetchedResultsController(fetchRequest:fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
        fetchResultController.delegate = self
        var e: NSError?
        var result = fetchResultController.performFetch(&e)
        todos = fetchResultController.fetchedObjects as [Todo]
        if result != true {
        println(e?.localizedDescription)
        } }
    var timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector:"update", userInfo:nil, repeats: true)
}
...
}

If I am going to invalidate the timer in AppDelegate.swift, how can I refer the timer to the one I created in TodoTableViewController.swift? Or maybe I should put all the code related to timer in AppDelegate.swift?


update

I have tried to use NSNotificationCenter, here is my code.

import UIKit
import CoreData

class TodoTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
...

var timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector:"viewDidLoad", userInfo:nil, repeats: true)

func update(){
    .....
}

override func viewDidLoad() {
    super.viewDidLoad()
    ...

    let notificationCenter = NSNotificationCenter.defaultCenter()

    notificationCenter.addObserver(self, selector: "didEnterBackground", name: "UIApplicationDidEnterBackgroundNotification", object: UIApplication.sharedApplication())

    notificationCenter.addObserver(self, selector: "didBecomeActive", name: "UIApplicationWillEnterForegroundNotification", object: UIApplication.sharedApplication())   
}


func didEnterBackground() {
    timer.invalidate()
}
func didBecomeActive() {
    var timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector:"update", userInfo:nil, repeats: true)
}

...
}

Since timer is declared in didBecomeActive(), in didEnterBackground() there is an error: does not have a member named "timer". If I declare timer outside didBecomeActive()like the code I posted above, there is an error saying "Extra argument 'selector' in call". I already parse the function update() to the selector. I don't know where this error comes from.

Antimony
  • 143
  • 1
  • 9
  • If you post the code you already have, then we can help you from there. Barring that, all I can say is to use timer.invalidate(). – Sean Dec 13 '14 at 03:39
  • 1
    Any class can subscribe to `UIApplicationDidEnterBackgroundNotification` and `UIApplicationWillEnterForegroundNotification` via NSNotificationCenter – Paulw11 Dec 13 '14 at 03:49
  • @SeanDMatthews Hi Sean, thanks for your suggestion. I just posted my code. The timer is created in the ViewDidLoad(). I am not sure if this is a good way to do it. – Antimony Dec 13 '14 at 06:28
  • @Paulw11 Hi Paulw. NotificationCenter is a good idea. But I am looking for a solution without using notification. I will try NSNotificationCenter later. Thanks! – Antimony Dec 13 '14 at 06:39
  • Depending on the code situation, it might be cleaner to have the AppDelegate handle the timer invalidation, since this timer is in some respects app-wide and the app delegate already gets the callback `applicationDidEnterBackground(application: UIApplication)` and `applicationDidBecomeActive(application: UIApplication)`. – Christian Di Lorenzo Dec 13 '14 at 13:45
  • @ChristianDiLorenzo That's what I am thinking. How do I create an app-wide timer? Do I create it in **AppDelegate.swift** or **TodoTableViewController.swift**? Or should I create another swift file **timer.swift**? – Antimony Dec 13 '14 at 18:01

2 Answers2

10

Updated your code and added few comments:

class TodoTableViewController: UITableViewController, NSFetchedResultsControllerDelegate 

   //better to instantiate timer inside viewDidLoad
   var timer: NSTimer!

   func update(){ }

   override func viewDidLoad() {
      super.viewDidLoad()
      startTimer()

      let notificationCenter = NSNotificationCenter.defaultCenter()

      //UIApplicationDidEnterBackgroundNotification & UIApplicationWillEnterForegroundNotification shouldn't be quoted
      notificationCenter.addObserver(self, selector: "didEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
      notificationCenter.addObserver(self, selector: "didBecomeActive", name: UIApplicationWillEnterForegroundNotification, object: nil)   
   }

   func didEnterBackground() {
      self.timer.invalidate()
   }

   func didBecomeActive() {
      startTimer()
   }

   func startTimer() {
      self.timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector:"update", userInfo:nil, repeats: true)
   }
}

Also notice that NSTimer strongly keeps it's target. So when closing viewcontroller - timer should be invalidated explicitely. Or memory leak will occur and TodoTableViewController will never be destroyed.

Mikhail
  • 4,271
  • 3
  • 27
  • 39
1

Answer for Objective-C, but I think it's very similar in Swift.

In ViewDidLoad add two notifications:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]];

and

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive) name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]];

Then add this to your view controller class.

- (void)didEnterBackground{
[_timer invalidate];
}

- (void)didBecomeActive{
_timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(update) userInfo:nil repeats:YES];
}
Senõr Ganso
  • 1,694
  • 16
  • 23
  • The timer is defined in didBecomeActive(). There is an error in didEnterBackground(): use of unresolved identifier "timer". How can I refer it to the one I created in didBecomeActive()? – Antimony Dec 13 '14 at 19:05
  • You should declare it first @property (nonatomic) NSTimer *timer; – Senõr Ganso Dec 14 '14 at 04:53