I am creating an app where a timer is set by the user. When the app goes to the background, the timer.invalidate(). Now I want the timer to start again when the app comes back to the foreground. I am creating another instance of timer to do it when the app sends notification that app is in the foreground. However, it's not firing the function.
In Viewdidload() I am creating a timer:
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(handleCountdown), userInfo: nil, repeats: true)
RunLoop.current.add(self.timer!, forMode: RunLoop.Mode.common)
And then I have notifications that check if the app is in background or in foreground:
When it enters background I am invalidating the timer.
#objc func applicationDidEnterBackground() {
let defaults = UserDefaults.standard
let quitTime = Date()
defaults.set(quitTime, forKey: "quitTimeKey") //Storing the time of quit in UserDefaults
timer?.invalidate()
}
When the app gets back out, I first check if the timer is isValid or not, and then create a new timer. But this timer doesn't seem to work.
#objc func appEntersForeground() {
calculateTimeLeft()
if let timer = timer {
if (!timer.isValid)
{
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(handleCountdown), userInfo: nil, repeats: true)
}
}
}
Some help here will be appreciated!
Declare your timer property as weak:
weak var timer: Timer?
Then it will be set to nil when the timer is invalidated. Then just check if timer is nil before creating a new one:
#objc func appEntersForeground() {
calculateTimeLeft()
if timer == nil {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(handleCountdown), userInfo: nil, repeats: true)
}
}
Related
I am trying to make a countdown timer that connects to a button, and am currently using the
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.clock), userInfo: nil, repeats: true)
However, I want it so if I press the same button a new timer starts with the similar settings, so in the console there are 2, 3, etc. countdown timers going at the same time.
How do I make it so whenever I press the button a new timer generates with same settings as the previous but the old one is still active and ticking down?
To keep track of the various timers, you want to create an array of timers ([Timer]) and create a TimerState to pass into each timer as the userInfo object. Then when updateTimer() is called, you can access the state for that timer as timer.userInfo and use it. When a timer reaches 0, call invalidate() and remove it from the list of timers.
Uses the timers array to stop all of the active timers if the user presses the Stop All Timers button.
import UIKit
class TimerState {
let number: Int
var count: Int
init(number: Int, count: Int) {
self.number = number
self.count = count
}
}
class ViewController: UIViewController {
var timerNumber = 1
var startingCount = 10
// Array to hold active timers so that all can be stopped
var timers = [Timer]()
#IBAction func startTimer(_ sender: UIButton) {
let state = TimerState(number: timerNumber, count: startingCount)
let timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTimer), userInfo: state, repeats: true)
timers.append(timer)
timerNumber += 1
}
#objc func updateTimer(_ timer: Timer) {
guard let state = timer.userInfo as? TimerState else { return }
state.count -= 1
if state.count == 0 {
print("Timer \(state.number) is done")
timer.invalidate()
// remove this timer from the list of active timers
if let index = timers.firstIndex(of: timer) {
timers.remove(at: index)
}
} else {
print("Timer \(state.number): \(state.count)")
}
}
#IBAction func stopAllTimers(_ sender: UIButton) {
for timer in timers {
guard let state = timer.userInfo as? TimerState else { continue }
print("Timer \(state.number) stopped")
timer.invalidate()
}
}
}
Pressing the Start Timer button 3 times yields the following output in the console:
Timer 1: 9
Timer 1: 8
Timer 1: 7
Timer 2: 9
Timer 1: 6
Timer 2: 8
Timer 1: 5
Timer 2: 7
Timer 3: 9
Timer 1: 4
Timer 2: 6
Timer 3: 8
Timer 1: 3
Timer 2: 5
Timer 3: 7
Timer 1: 2
Timer 2: 4
Timer 3: 6
Timer 1: 1
Timer 2: 3
Timer 3: 5
Timer 1 is done
Timer 2: 2
Timer 3: 4
Timer 2: 1
Timer 3: 3
Timer 2 is done
Timer 3: 2
Timer 3: 1
Timer 3 is done
Based on your code:
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.clock), userInfo: nil, repeats: true)
I would assume that you are declaring timer as an instance variable in ViewController something like:
class ViewController: UIViewController {
var timer: Timer?
#IBAction func tapped(_ sender: Any) {
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.clock), userInfo: nil, repeats: true)
}
#objc func clock() {
print("counting...")
}
}
So each tap on the button, it will schedule a new timing session even if you are using the same Timer instance. In this case, what you should do is to invalidate timer each time before assigning Timer.scheduledTimer to it:
Stops the timer from ever firing again and requests its removal from
its run loop.
In the button action method, add timer?.invalidate() in the first:
#IBAction func tapped(_ sender: Any) {
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.clock), userInfo: nil, repeats: true)
}
Create a new Timer() instance for each button press (if you do indeed want multiple timers) with the same properties.
Would be worth keeping a list of all of your Timers though so you can deal with them when you no longer need them (so you don't have many tens of timers building up if someone spammed the button).
Well, it depends on whether you need to track all of those timers or not. If not is pretty straightforward. Just create a timer each time you click on the button:
func buttonDidClick() {
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.clock), userInfo: nil, repeats: true)
}
if you need to keep track of those timers use an Array:
func buttonDidClick() {
myArray.append(Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.clock), userInfo: nil, repeats: true))
}
This way you can even cancel all the timers or do anything you need.
I want to call the method func adjustmentBestSongBpmHeartRate() every 1.1 second. I used Timer, but it doesn't work. I have read the document and found a lot of sample code, it still does work! Is there anything I missed?
timer = Timer.scheduledTimer(timeInterval: 1.1, target: self, selector: #selector(self.adjustmentBestSongBpmHeartRate), userInfo: nil, repeats: false)
timer.fire()
func adjustmentBestSongBpmHeartRate() {
print("frr")
}
I found that creating the timer in an OperationQueue Operation did not work. I assume this is because there is no runloop.
Therefore, the following code fixed my problem:
DispatchQueue.main.async {
// timer needs a runloop?
self.timeoutTimer = Timer.scheduledTimer(timeInterval: self.timeout, target: self, selector: #selector(self.onTimeout(_:)), userInfo: nil, repeats: false)
}
Timer methods with a selector are supposed to have one parameter: The timer itself. Thus your code should really look like this: 1
Timer.scheduledTimer(timeInterval: 1.1,
target: self,
selector: #selector(self.adjustmentBestSongBpmHeartRate(_:),
userInfo: nil,
repeats: false)
#objc func adjustmentBestSongBpmHeartRate(_ timer: Timer) {
print("frr")
}
Note that if your app only runs on iOS >= 10, you can use the new method that takes a block to invoke rather than a target/selector. Much cleaner and more type-safe:
class func scheduledTimer(withTimeInterval interval: TimeInterval,
repeats: Bool,
block: #escaping (Timer) -> Void) -> Timer
That code would look like this:
timer = Timer.scheduledTimer(withTimeInterval: 1.1,
repeats: false) {
timer in
//Put the code that be called by the timer here.
print("frr")
}
Note that if your timer block/closure needs access to instance variables from your class you have to take special care with self. Here's a good pattern for that sort of code:
timer = Timer.scheduledTimer(withTimeInterval: 1.1,
repeats: false) {
//"[weak self]" creates a "capture group" for timer
[weak self] timer in
//Add a guard statement to bail out of the timer code
//if the object has been freed.
guard let strongSelf = self else {
return
}
//Put the code that be called by the timer here.
print(strongSelf.someProperty)
strongSelf.someOtherProperty = someValue
}
Edit (updated 15 December)
1: I should add that the method you use in the selector has to use Objective-C dynamic dispatch. In Swift 4 and later, the individual methods you reference must be tagged with the #objc tag. In previous versions of Swift you could also declare the entire class that defines the selector with the #objc qualifier, or you could make the class that defined the selector a subclass of NSObject or any class that inherits from NSOBject. (It's quite common to define the method the timer calls inside a UIViewController, which is a subclass of NSObject, so it used to "just work".
Swift 3
In my case it worked after I added to my method the #obj prefix
Class TestClass {
private var timer: Timer?
func start() {
guard timer == nil else { return }
timer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(handleMyFunction), userInfo: nil, repeats: false)
}
func stop() {
guard timer != nil else { return }
timer?.invalidate()
timer = nil
}
#objc func handleMyFunction() {
// Code here
}
}
Try this -
if #available(iOS 10.0, *) {
self.timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false, block: { _ in
self.update()
})
} else {
self.timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(self.update), userInfo: nil, repeats: false)
}
Mostly the problem must have been because of iOS version of mobile.
Swift 5, Swift 4 Simple way only call with Dispatch Queue Async
DispatchQueue.main.async
{
self.andicator.stopAnimating()
self.bgv.isHidden = true
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false, block: { _ in
obj.showAlert(title: "Successfully!", message: "Video save successfully to Library directory.", viewController: self)
})
}
I have solved the question asked by myself.
I'm using apple watch to control my iphone app.
I try to press a button on apple watch to present a new viewcontroller on iphone.
When I write Timer in override func viewDidLoad(), Timer doesn't work. I move Timer to override func viewWillAppear() it works.
I think maybe there's something wrong with controlling by apple watch
I found that if you try to initialize the timer directly at the class-level, it won't work if you're targeting a selector in that same class. When it fires, it can't find the selector.
To get around this, I only initialize the timer after the object containing the selector has been initialized. If it's in the same class, put the initialization code in the ViewDidLoad or similar. Just not in the initializer. Then it will work. No dispatch queue needed.
Also, you do not need to use a selector that accepts the timer as a parameter. You can, but contrary to the answer with a ton of votes, that's not actually true, or more specifically, it works fine for me without it, just as you have it without it.
By the way, I think the reason the dispatch queue worked is because you're forcing the timer to be created after the object was initializing, confirming my above statement.
let timer:Timer?
override func viewDidLoad(){
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 1.1, target: self, selector: #selector(adjustmentBestSongBpmHeartRate), userInfo: nil, repeats: false)
timer.fire()
}
func adjustmentBestSongBpmHeartRate() {
print("frr")
}
Note: This is code typed from memory, not copied from Xcode so it may not compile, but hopefully you get the idea.
Swift3
var timer = Timer()
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.compruebaConexion), userInfo: nil, repeats: true)
my two cents.
I read about "didLoad" and when invoking it.
so we can use a delay:
class ViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
startTimer()
}
final func killTimer(){
self.timer?.invalidate()
self.timer = nil
}
final private func startTimer() {
// make it re-entrant:
// if timer is running, kill it and start from scratch
self.killTimer()
let fire = Date().addingTimeInterval(1)
let deltaT : TimeInterval = 1.0
self.timer = Timer(fire: fire, interval: deltaT, repeats: true, block: { (t: Timer) in
print("hello")
})
RunLoop.main.add(self.timer!, forMode: RunLoopMode.commonModes)
}
I have this problem for a few days now and I don't get what I am doing wrong.
My application is basically just creating some timers. I need to stop them and create new ones. But at the moment stopping them doesn't work.
self.timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target:self, selector: "timerDidEnd:", userInfo: "Notification fired", repeats: false)
That's my timer
func timerDidEnd(timer:NSTimer){
createUnrepeatedAlarmWithUpdateInterval()
}
Because my timer didn't want to stop I am currently using the unrepeated timer and start it myself after it stopped.
func stopAlarm() {
if self.timer != nil {
self.timer!.invalidate()
}
self.timer = nil
self.timer = NSTimer()
}
And that's how I stop my timer.
alarmManager.stopAlarm()
alarmManager.createUnrepeatedAlarmWithUpdateInterval()
I call the stopAlarm() function before creating a new timer.
I really don't know what I am doing wrong so I appreciate every answer :)
class AlarmManager: ViewController{
private var timer : NSTimer?
private var unrepeatedTimer : NSTimer?
private let notificationManager = NotificationManager()
private var current = NSThread()
private let settingsViewController = SettingsViewController()
func createRepeatedAlarmWithUpdateInterval(){
var timeInterval:NSTimeInterval = settingsViewController.getUpdateIntervalSettings()
if timer == nil{
timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
target: self,
selector: "repeatedTimerDidEnd:",
userInfo: "Notification fired",
repeats: true)
}
}
func repeatedTimerDidEnd(repeatedTimer:NSTimer){
ConnectionManager.sharedInstance.loadTrainings(settingsViewController.getServerSettings())
createUnrepeatedAlarm(10)
}
func createUnrepeatedAlarm(timeInterval:Double){
unrepeatedTimer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
target: self,
selector: "unrepeatedTimerDidEnd:",
userInfo: "Notification fired",
repeats: false)
}
func unrepeatedTimerDidEnd(unrepeatedTimer:NSTimer){
notificationManager.createNotification(self, reminderType: NotificationManager.ITEMRATINGREMINDER)
notificationManager.createNotification(self, reminderType: NotificationManager.ITEMREMINDER)
print("UnrepeatedAlarm ended")
}
func stopAlarm(){
print("StopAlarm triggered")
if (timer != nil)
{
print("stoptimer executed")
timer!.invalidate()
timer = nil
}
if (unrepeatedTimer != nil)
{
unrepeatedTimer!.invalidate()
unrepeatedTimer = nil
}
}
}
Thats the whole code of this class. Maybe that helps :D
The usual way to start and stop a timer safely is
var timer : Timer?
func startTimer()
{
if timer == nil {
timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
}
}
func stopTimer()
{
timer?.invalidate()
timer = nil
}
startTimer() starts the timer only if it's nil and stopTimer() stops it only if it's not nil.
You have only to take care of stopping the timer before creating/starting a new one.
Make sure you're calling invalidate on the same thread as the timer.
From the documentation:
Special Considerations
You must send this message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.
https://developer.apple.com/documentation/foundation/nstimer/1415405-invalidate?language=objc
Something that's not really covered by the previous answers is that you should be careful your timer isn't scheduled multiple times.
If you schedule a timer multiple times without first invalidating it, it'll end up scheduled on multiple run loops, and invalidating it then becomes nigh impossible.
For me, it happened when calling my scheduleTimer() function in separate functions in my view controller's life cycle (viewWillAppear, viewDidAppear, ...)
So in short, if you aren't sure (or you cannot guarantee) your Timer is only scheduled once, just always invalidate it first.
I have tried every possible solution found but not able to resolve that at the end I have set repeat "false" while initialising timer like below
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(viewcontroller.methodname), userInfo: nil, repeats: false)
And need to add above line in my selector method for whatever the condition for which I wanted to repeat the time.
For example:-
My requirement is I want to repeatedly call some method until one condition satisfied. So instead of adding repeats true I set it false as repeat true does not invalidate timer in my case.
I have added below in my viewdidload method
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(viewcontroller.method), userInfo: nil, repeats: false)
in selector function I added below code
#objc func method{
if condition not matched{
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(viewcontroller.method), userInfo: nil, repeats: false)
}
else{
// once you are here your timer invalidate automatically
}
}
Hope this will solve your problem
For Swift 5 Xcode 12.4 there is example to use timer:
class MyController: UIViewController {
private id: Float;
func setValue(_ value: Float, withAnimation: Bool) {
let step: Float = value / 200
var current: Float = withAnimation ? 0.0 : value
let _ = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: withAnimation) { timer in
DispatchQueue.main.async {
self.id = current
current += step
if current > value || withAnimation == false {
self.id = current
timer.invalidate()
}
}
}
}
}
I am trying to use the NSTimer to increment the progress bar in my app when recording voice (see the screenshot)
let timedClock = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("Counting:"), userInfo: nil, repeats: true)
internal func Counting(timer: NSTimer!) {
if timeCount == 0 {
//self.timedClock = nil
stopRecording(self) //performs segue to another view controller
} else {
timeCount--;
self.timer.text = "\(timeCount)"
}
print("counting called!")
progressBar.progress += 0.2
}
The progress bar works only for the first time after I compile and run the project. When the recording is finished, the app performs segue to another view controller to play the recorded audio. However, when I go back to the view for recording, the timer/progress bar automatically runs. I suspect the NSTimer object is still alive on the NSRunLoop. So I was wondering how to prevent the NSTimer from automatically running.
Inspired by the answer in this SO thread, I tried the following, but the NSTimer still automatically runs.
let timedClock = NSTimer(timeInterval: 1, target: self, selector: "Counting:", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timedClock, forMode: NSRunLoopCommonModes)
This happens because when your controller created it's properties are automatically initialized. According to Apple Docs (and method's name) scheduledTimerWithTimeInterval create and return scheduled timer. So if you only want create your timer and call it by trigger function use it like this:
class MyClass {
var timer: NSTimer?
...
func enableTimer() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("Counting:"), userInfo: nil, repeats: true)
}
func disableTimer() {
timer?.invalidate()
timer = nil
}
...
}
Sorry for the quick self-answer, as I just found out that I can use the invalidate() method to prevent the timer from automatically firing:
timedClock.invalidate()
Hope it helps someone in the future!
I've made a flappy bird clone, but when trying to implement pause functionality, I've come across issues with my NSTimer, which is spawning the pipes.
var timer = NSTimer.scheduledTimerWithTimeInterval(moveSpeed, target: self, selector: Selector("makePipes"), userInfo: nil, repeats: true)
As NSTimers can't be paused, within the makePipes() function I've implemented a simple if-statement, checking whether the game is paused or not, and if it is, not spawning new pipes. However, the gap between each series of pipes is then inconsistent, due to the timer still firing during the paused state.
Also, when transitioning to the game state from the menu, the timer fires off-time, creating the first two pipes in quick succession.
Is there any alternative to the NSTimer to handle this functionality?
You can stop the timer calling the func invalidate() when your game is paused, and then restart it when your game is un-paused.
Update:
You can add a second timer that fires at the difference from the next fire and the time of pause, the second timer should fire the first timer and then reset the first timer to the initial time.
Steps:
Add lets say a timer that fires every 2 seconds
When game is paused, calculate the time interval from the timer.fireDate and timeOfPause, that should be intervalTillNextTrigger
Add a second afterPauseTimer that triggers at intervallTillNextTrigger and should not repeat
When the afterPauseTimer is called, trigger the first timer with timer.fire(), invalidate timer, because timer.fire() will not interrupt it's regular firing schedule, add timer again with 2 seconds firing interval and invalidate afterPauseTimer.
see code below:
//
// ViewController.swift
// swft ios
//
// Created by Marius Fanu on 30/12/14.
// Copyright (c) 2014 Marius Fanu. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
var timer: NSTimer!
var isPaused = false
var isAfterPause = false
var intervalTillNextTrigger: NSTimeInterval = 0
var afterPauseTimer: NSTimer!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
timer = NSTimer(timeInterval: 2, target: self, selector: Selector("timerTriggerd"), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSDefaultRunLoopMode)
timer.fire()
}
#IBAction func pauseButtonPressed(sender: UIButton) {
var now = NSDate()
println("now = \(now)")
if isPaused == true {
if isAfterPause {
isAfterPause = false
afterPauseTimer = NSTimer(timeInterval: intervalTillNextTrigger, target: self, selector: Selector("timerAfterIntervalTrigger"), userInfo: nil, repeats: false)
NSRunLoop.mainRunLoop().addTimer(afterPauseTimer, forMode: NSDefaultRunLoopMode)
}
timer = NSTimer(timeInterval: 2, target: self, selector: Selector("timerTriggerd"), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSDefaultRunLoopMode)
}
else {
isAfterPause = true
intervalTillNextTrigger = timer.fireDate.timeIntervalSinceDate(now)
println("till next trigger \(intervalTillNextTrigger)")
timer.invalidate()
timer = nil
}
isPaused = !isPaused
}
func timerTriggerd() {
NSLog("Triggerd!")
}
func timerAfterIntervalTrigger() {
println("reset timer")
timer.fire()
timer.invalidate()
timer = nil
timer = NSTimer(timeInterval: 2, target: self, selector: Selector("timerTriggerd"), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSDefaultRunLoopMode)
afterPauseTimer.invalidate()
afterPauseTimer = nil
}
}