Trying to implement beginBackgroundTaskWithExpirationHandler and UILocalNotification - swift

I have the following code in my AppDelegate for when my application enters the background:
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
func beginBackgroundUpdateTask() {
self.backgroundUpdateTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
UIApplication.sharedApplication().endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
func doBackgroundTask() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
self.beginBackgroundUpdateTask()
// Do something with the result.
var timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: "displayAlert", userInfo: nil, repeats: false)
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSDefaultRunLoopMode)
NSRunLoop.currentRunLoop().run()
// End the background task.
self.endBackgroundUpdateTask()
})
}
func displayAlert() {
let note = UILocalNotification()
note.alertBody = "As a test I'm hoping this will run in the background every X number of seconds..."
note.soundName = UILocalNotificationDefaultSoundName
UIApplication.sharedApplication().scheduleLocalNotification(note)
}
func applicationDidEnterBackground(application: UIApplication) {
self.doBackgroundTask()
}
I'm hoping that it executes a UILocalNotification() every X number of seconds specified in the NSTimer.scheduledTimerWithTimeInterval() however it only executes once.
I'm still trying to get my head around how background tasks work. Is there something I'm missing?

In the code sample, the timer you create will only fire once as you have set the "repeats" value to false in the initialiser.

Related

Run Background Timer Swift

I solved the problem.
var backgroundUpdateTask: UIBackgroundTaskIdentifier = 0
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
return true
}
func applicationWillResignActive(application: UIApplication) {
self.backgroundUpdateTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
UIApplication.sharedApplication().endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
func applicationWillEnterForeground(application: UIApplication) {
self.endBackgroundUpdateTask()
}
Timer doesn't work until it's activated. I want to do the timer update process even when the application is in the background. I update the current time with the tick function and when the timer is synchronized with the power on / off timer, I do the turn on or off. I want timer and the update process to work in the background.
override func viewDidLoad()
{
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector:#selector(self.tick) , userInfo: nil, repeats: true)
}
#objc func tick() {
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yyyy HH:mm"
labelTimer.text = formatter.string(from: Date())
zaman()
zaman1()
}
#objc func zaman(){
if timertext.text == String? (labelTimer.text!) {
zamanlayıcıfunc()
}else{
return
}
}
#objc func zamanlayıcıfunc()
{
if labelcheckbox.text == ("aç"){
updateState()
}
if labelcheckbox.text == ("kapat"){
updateState1()
}
}
#objc func zaman1(){
if timertext2.text == String? (labelTimer.text!) {
zamanlayıcıfunc1()
}else{
return
}
}
#objc func zamanlayıcıfunc1()
{
if labelcheckbox2.text == ("saatinde kapat"){
updateState1()
}
else{
updateState()
}
}
#objc func updateState(){
let ref = Database.database().reference()
ref.child("\(chip1InfoString1!)/states/\(self.ekle2.text!)").setValue(true)
getData()
}
Your application will not continue processing in the background. If all applications could do that the phone battery would easily and quickly be drained.
The OS will provide you with some limited background execution time, but if you use up too many resources it will be further limited. You can read more about it in Apple's documentation.
What you may need to do is keep track of when the app went into the background and foreground, using UIApplication.didEnterBackgroundNotification and UIApplication.willEnterForegroundNotification, to see how much time has passed.

How to execute a method every second on a background thread so it doesn't affect the performance of the app

I am trying to access my database every 30 seconds, however, whenever the method executes I can clearly see a performance dip in the application.
So far this is my current code:
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
scheduledTimerWithTimeInterval()
}
func scheduledTimerWithTimeInterval(){
timer = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(self.updateCounting), userInfo: nil, repeats: true)
}
#objc func updateCounting(){
getDatabaseInfo()
}
I am trying to do the same thing, except I would like to execute the getDatabaseInfo() method on a background thread so that the app's performance is not compromised.
You can just run the timer directly on the background queue by using a DispatchSourceTimer:
private var timer: DispatchSourceTimer?
func startTimer() {
let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".timer")
timer = DispatchSource.makeTimerSource(queue: queue)
timer!.schedule(deadline: .now(), repeating: .seconds(1))
timer!.setEventHandler { [weak self] in
// do whatever stuff you want on the background queue here here
getDatabaseInfo()
DispatchQueue.main.async {
// update your model objects and/or UI here
}
}
timer!.resume()
}
func stopTimer() {
timer?.cancel()
timer = nil
}
Using Timer, you schedule it on the main queue's run loop and then have it dispatch the task to a background queue, and then dispatch UI/model updates back to the main queue. Using dispatch timer, like above, bypasses that first step, running the timer directly on a GCD background queue.
Use Grand Central Dispatch :
DispatchQueue.global(qos: .background).async {
getDatabaseInfo()
}
You can try with below code.
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
scheduledTimerWithTimeInterval()
}
func scheduledTimerWithTimeInterval(){
timer = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(self.updateCounting), userInfo: nil, repeats: true)
}
#objc func updateCounting(){
DispatchQueue.global(qos: .background).async {
print("This is run on the background queue")
getDatabaseInfo()
DispatchQueue.main.async {
print("This is run on the main queue, after the previous code in outer block")
}
}
}

Why does my timer in swift keep speeding up?

I am creating a trivia app in swift and I have a timer that counts down each question. However as the user progresses with each question the timer speeds up. Can someone help me fix this?
My runGameTimer function:
func runGameTimer()
{
gameTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(RockTriviaViewController.updateGameTimer), userInfo: nil, repeats: true)
}
My updateGameTimer function:
#objc func updateGameTimer()
{
gameInt -= 1
timerLabel.text = String(gameInt)
if (gameInt == 0)
{
gameTimer.invalidate()
/*
if (currentQuestion != rockQuestions[questionSet].count)
{
newQuestion()
}
else
{
performSegue(withIdentifier: "showRockScore", sender: self)
}
*/
}
}
Where I call my code:
func newQuestion()
{
gameInt = 11
runGameTimer()
rockQuestion.text = rockQuestions[questionSet][currentQuestion]
rightAnswerPlacement = arc4random_uniform(3)+1
var Button: UIButton = UIButton()
var x = 1
for i in 1...3
{
Button = view.viewWithTag(i) as! UIButton
if(i == Int(rightAnswerPlacement))
{
Button.setTitle(rockAnswers[questionSet][currentQuestion][0], for: .normal)
}
else
{
Button.setTitle(rockAnswers[questionSet][currentQuestion][x], for: .normal)
x = 2
}
}
currentQuestion += 1
}
You're calling runGameTimer() in every call to newQuestion(). If a timer was already running then you'll add a new timer each time, and they will all call your selector. So if you have 3 timers running, your selector will be called 3x as often. That's not what you want.
Change your timer variable to be weak:
weak var gameTimer: Timer?
And then in runGameTimer invalidate the timer before creating a new one, using optional chaining:
func runGameTimer() {
gameTimer?.invalidate() //This will do nothing if gameTimer is nil.
//it will also cause the gameTimer to be nil since it's weak.
gameTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(RockTriviaViewController.updateGameTimer), userInfo: nil, repeats: true)
}
By making the game timer weak it will get set to nil as soon as it's invalidated. (When you schedule a timer the system retains it while it is running so it stays valid as long as it continues to run.)
By using optional chaining to reference the timer:
gameTimer?.invalidate()
The code doesn't do anything if gameTimer is nil.

background run timer swift

I want the timer to run even when I close the application. I want it to work in the background counter. the timer goes back one second when I run it.(counter) How can I do that?
class TimerViewController: UIViewController {
var selectedDay: String?
var seconds =
var timer = Timer()
#IBAction func start(_ sender: AnyObject) {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(TimerViewController.counter), userInfo: nil, repeats: true)
sliderOutlet.isHidden = true
startOutlet.isHidden = true
}
#objc func counter() {
seconds -= 1
favoriteDayTextField.text = String(seconds) + " Seconds"
var bgTask = UIBackgroundTaskIdentifier(rawValue: seconds)
bgTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(bgTask)
})
if (seconds == 0) {
timer.invalidate()
if self.button.isOn {
updateState()
} else {
updateState1()
}
}
}
}
I am not clear what you want to achieve. Suppose you want to update the label after the timer has started each 1 second. Then one approach will be:-
Start the timer in view did load if the duration is remaining.
Register for applicationWillTerminate
In application will terminate save the passed duration and terminated time to calculate remaining time in next launch.
var remainingDuration: TimeInterval!
override func viewDidLoad() {
super.viewDidLoad()
let remainingDurationFromLastLaunch = UserDefaults.standard.object(forKey: "duration") as? TimeInterval ?? 0
let lastTerminatedTime = UserDefaults.standard.object(forKey: "lastTerminatedDate") as? Date ?? Date()
if Date().timeInterval(since: lastTerminatedTime) > remainingDurationFromLastLaunch {
remainingDuration = remainingDurationFromLastLaunch - Date().timeInterval(since: lastTerminatedTime)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(TimerViewController.counter), userInfo: nil, repeats: true)
NotificationCenter.default.addObserver(self, selector: #selector(TimerViewController.applicationWillTerminate), name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
} else { //Duration is passed....Do whatever you want
}
}
#objc func counter() {
remainingDuration -= 1
if remainingDuration == 0 { //Duration is passed....Do whatever you want
timer.invalidate()
timer = nil
} else {
favoriteDayTextField.text = String(remainingDuration) + " Seconds"
}
}
#objc func applicationWillTerminate() {
if timer != nil {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
UserDefaults.standard.set(remainingDuration, forKey: "duration")
UserDefaults.standard.set(Date(), forKey: "lastTerminatedDate")
}
self?.endBackgroundTask()
}
}
func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
The only way for your iOS application to perform some action even while it is in the background is to use Background Modes .
However you cannot perform anything and everything while your
application is in background
There are certain limitations to the type of tasks that you can perform . I have attached a really good article for your reference
Background Modes Tutorial
However, I am not sure if you can initiate and continue a timer sort of functionality while your application is in background
Though, keep in mind , once your application is closed (i.e. by double tapping the home button and swiping the application window up to close it completely) , not even Background modes work at that point because the user does not want to run your app anymore, even in the background

How to stop backgroundUpdateTask?

Here is code which I execute in background when user close the app, but it is weird behavior , after endBackgroundUpdateTask() method is executed , DispatchQueue still doesn't stops...
I steel continuos get notification.
What am I doing wrong?
you can try to take this snipped of code and try for yourself, it is really weird
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
func beginBackgroundUpdateTask() {
print("beginBackgroundUpdateTask")
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
print("endBackgroundUpdateTask")
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
func doBackgroundTask() {
print("Strart")
DispatchQueue.global().async {
self.beginBackgroundUpdateTask()
// Do something with the result.
let timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(AppDelegate.displayAlert), userInfo: nil, repeats: true)
RunLoop.current.add(timer, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
// End the background task.
self.endBackgroundUpdateTask()
}
print("Finish")
}
func displayAlert() {
print("displayAlert")
let note = UILocalNotification()
note.alertBody = "As a test I'm hoping this will run in the background every X number of seconds..."
note.soundName = UILocalNotificationDefaultSoundName
UIApplication.shared.scheduleLocalNotification(note)
}
func applicationDidEnterBackground(_ application: UIApplication) {
log.debug("applicationDidEnterBackground")
self.doBackgroundTask()
}
edit
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
var timerr: Timer?
func beginBackgroundUpdateTask() {
appDeligate.log.debug("beginBackgroundUpdateTask")
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
appDeligate.log.debug("endBackgroundUpdateTask")
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
timerr = nil
}
func doBackgroundTask() {
print("Strart")
DispatchQueue.global().async {
self.beginBackgroundUpdateTask()
// Do something with the result.
self.timerr = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(TestViewController.displayAlert), userInfo: nil, repeats: true)
RunLoop.current.add(self.timerr!, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
// End the background task.
self.endBackgroundUpdateTask()
}
print("Finish")
}
func displayAlert() {
print("displayAlert")
let note = UILocalNotification()
note.alertBody = "As a test I'm hoping this will run in the background every X number of seconds..."
note.soundName = UILocalNotificationDefaultSoundName
UIApplication.shared.scheduleLocalNotification(note)
}
Really issue was with Timer, I was needed put this line
timerr?.invalidate()
here
func endBackgroundUpdateTask() {
appDeligate.log.debug("endBackgroundUpdateTask")
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
timerr?.invalidate()
}
But anyway it is little bit weird , because I thought if I was stopping backgroundUpdate() all tasks inside had to invalidate and purge automatically, but no.
Thanks #matt