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.
Problem is, NSTimer requires an active run loop which is not always readily available on background queues. The main thread has an active run loop but this defeats the purpose of having our timer run in the background so its a definite no go. So, to get a dedicated background-queue-friendly timer, we use GCD.
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.
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];
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With