my timer worked well even in Background-Mode ;-) I save the didEnterBG tTime in Userdefaults and calculate the differenz in the WillEnterForeground.
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
My app start with an TABBAR-VC and than goes deeper via NAVIGATION-VC
Now if I start the timer and toggle between the TABBAR-VC the timerlable is updated well - BUT if I use the BACK Button from the NAVIGATION-VC and come back later the timerlable got the default time - no action for update, but the timer is still active (output from the debuger).
So what is missing - that the VC know that he has to update the lable again?
#objc func updateTimer() {
if seconds == MY_TIME {
print("savePoints")
savePoints()
}
if seconds == 1 {
self.playSound()
}
if seconds < 1 {
clearTimer()
saveAndReload()
print("Aufgabe beendet")
btPauseTimer.isEnabled = false
btStopTimer.isEnabled = false
btPauseTimer.setTitle("--", for: UIControlState.normal)
btStopTimer.setTitle("--", for: UIControlState.normal)
} else {
seconds -= 1
timerLabel.text = timeString(time: TimeInterval(seconds))
}
}
Got it on my own ;-)
I declare all VAR I need outside of my Controller.swift class (timer etc...)
If the timer isRunning and I comeback to this VC - I call updateLabel() inside the viewAppears. This updateLabel method is another scheduled timer like the other obove, calling a method newString() with only this line every second ;-)
timerLabel.text = timeString(time: TimeInterval(seconds))
EDIT:
This solution is the right way for me (I hope so.. ;-)
#objc func updateLabel(){
if isBasicTimerRunning{
timerLabel!.text = timeString(time: TimeInterval(secondsBasic))
perform(#selector(updateLabel), with: nil, afterDelay: 0.01)
}else{
NSObject.cancelPreviousPerformRequests(withTarget: self)
}
}
perform#selector tick the same method as long my timer is running - if not than the tick is canceled.
Related
I have created one timer object and set #selector method, In #selector method my label update every time that display timer count down value, but when I push or pop another view controller and come back to timer view controller my label not updating timer count down value
override func viewDidLoad() {
super.viewDidLoad()
if timer == nil {
self.startTimer()
}
}
func startTimer() {
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval:1, target: self, selector: #selector(self.update), userInfo: nil, repeats: true);
}
func update() {
count += 1
if(count > 0){
let ti = NSInteger(count)
let strSeconds = ti % 60
let strMinutes = (ti / 60) % 60
let strHours = (ti / 3600)
print("\(strHours):\(strMinutes):\(strSeconds)")
self.lblTimer.text = String(format: "%0.2d:%0.2d:%0.2d",strHours,strMinutes,strSeconds)
}
}
When you use the selector try self.update() to actually call the function. May also put a print statement to check that your variable is indeed incrementing.
I have a problem with two timers running in the same View Controller. I need one of them to launch when the other is invalidated and go back on after the start button for the first is tapped again. I tried creating two variables and it builds successfully, but the behavior is erratic. What would be the right approach? Thanks
#IBAction func Start(_ sender: AnyObject) {
timer2.invalidate()
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
}
#IBAction func pauseTimer(_ sender: AnyObject) {
timer.invalidate()
timer2 = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: Selector(("increaseTimer")), userInfo: nil, repeats: true)
}
So you need a sort of "flip flop" timer effect?
Hav you thought about replacing var timerHasFinishedRunning: Bool = false with checking the invalidation?
e.g.
// When you invalidate, rather
timer1.invalidate()
timer1 = nil
// As goes for timer2
timer2.invalidate()
timer2 = nil
That way, you can have your timer checking done view computed properties:
var timer1HasFinishedRunning: Bool {
return self.timer1 == nil
}
var timer1HasFinishedRunning: Bool {
return self.timer2 == nil
}
Also you mention that they behave "erratically", could you elaborate? Your timer interval is 1 second, so if anything erratic happens, "within 1 second" it's probably because of the long interval. e.g. the checking of each is only done once per second, so sometimes could take as long as 1.999999 seconds to notice that a timer was invalidated.
Personally, I'd have the interval at 0.1 rather than 1.0 for greater accuracy.
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 have a problem with invalidating different timers.
I have multiple timers (NSTimer) on a viewcontroller(settingsVC):
class settingsVC: UIViewController {
// I use 12 timers
var timer1 = NSTimer()
// Seconds to end the timer. Set 12 timers
let timeInterval1:NSTimeInterval = 10
var timer2 = NSTimer()
let timeInterval2:NSTimeInterval = 20
var timer3 = NSTimer()
let timeInterval3:NSTimeInterval = 30
//and so on ... 12 timers
}
With a UIButton (Start) a segue is performed. And for every different value of the variable 'picked', a different timer will be started in the same class:
class settingsVC: UIViewcontroller {
let defaults = NSUserDefaults.standardUserDefaults()
let pickerDefaultsIntegerKey = "Picker" // nsuserdefaults key
#IBAction func start(sender: AnyObject) {
// segue to another viewcontroller
performSegueWithIdentifier("timerOn", sender: self)
if picked == 1 {
defaults.setInteger(1, forKey: pickerDefaultsIntegerKey)
timer1 = NSTimer.scheduledTimerWithTimeInterval(timeInterval1,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: false)
print("timer1 started")
} else if picked == 2 {
defaults.setInteger(2, forKey: pickerDefaultsIntegerKey)
timer2 = NSTimer.scheduledTimerWithTimeInterval(timeInterval2,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: false)
print("timer2 started")
} else if // ....and so on{........ }
}
The method fired if timer ends, see selector:
func timerDidEnd(timer:NSTimer){
print("timer ended")
// do other stuff
}
I invalidate the timers with a button (Reset) for values from a variable ('pickerSavedSelection') which is updated by saved values in NSUserdefaults:
#IBAction func reset(sender: AnyObject) {
if let pickerSavedSelection = defaults.integerForKey(pickerDefaultsIntegerKey) as Int?
{
if pickerSavedSelection == 1 {
timer1.invalidate()
} else if pickerSavedSelection == 2 {
timer2.invalidate()
} else if //...and so on{....}
}
All goes well, if I outcomment the perform segue line and just let the user stay on this viewcontroller.The timers get invalidated correctly then:
In the console I read 'timer1 started' and I do NOT read 'timer ended' when the resetButton is pressed.
But staying on this viewcontroller(settingsVC) is NOT the flow of my app.
When the perform segue line is executed and the user 'comes back' to the viewcontroller (settingsVC), the timers are not invalidated when user presses the resetButton:
In the console I read 'timer1 started' and I DO read 'timer ended' when the resetButton is pressed.
How should I stop the timers, when users will 'exit' the viewcontroller and come back to reset the timers?
Help is much appreciated! Thanks in advance
If I am not mistaken at any given point in time, you are only triggering one NSTimer. All your different timers are differentiated only in time intervals. So, my suggestion would be to keep only one NSTimer and have your time interval differentiated. With different value picked you should first invalidate the timer and then restart it with new time interval. That said, your reset will then be much simplified and you do not need to save pickerSavedSelection in NSUserDefaults. This is how I would re-write this code:
class settingsVC: UIViewController {
var timer = NSTimer()
#IBAction func start(sender: AnyObject) {
// segue to another viewcontroller
performSegueWithIdentifier("timerOn", sender: self)
if picked == 1 {
self.timer.invalidate()
self.timer = NSTimer.scheduledTimerWithTimeInterval(10,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: false)
print("timer1 started")
} else if picked == 2 {
self.timer.invalidate()
self.timer = NSTimer.scheduledTimerWithTimeInterval(20,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: false)
print("timer2 started")
} else if // ....and so on{........ }
}
#IBAction func reset(sender: AnyObject) {
self.timer.invalidate()
}
}
PS: As a side note, I would advise your NSTimer to start & stop from main thread. Use GCD for that.
It is because your selector is not called when your timer is invalidated, it is called everytime your timer is fired. Since the timer is non-repeat, the selector get called only once. When your press reset button, timer is actually invalidated, you just didn't know because you misunderstood scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: method.
I am new to swift programming and I don't know how to call a method at regular interval of time. I have a demo app for service call but i don't know how can i call it at regular interval of time.
You can create an object of NSTimer() and call a function on definite time interval like this:
var updateTimer = NSTimer.scheduledTimerWithTimeInterval(15.0, target: self, selector: "callFunction", userInfo: nil, repeats: true)
this will call callFunction() every 15 sec.
func callFunction(){
print("function called")
}
Here is a simple example with start and stop functions:
private let kTimeoutInSeconds:NSTimeInterval = 60
private var timer: NSTimer?
func startFetching() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(kTimeoutInSeconds,
target:self,
selector:Selector("fetch"),
userInfo:nil,
repeats:true)
}
func stopFetching() {
self.timer!.invalidate()
}
func fetch() {
println("Fetch called!")
}
If you get an unrecognized selector exception, make sure your class inherits from NSObject or else the timer's selector won't find the function!
Timer variant with a block (iOS 10, Swift 4)
let timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { (timer) in
print("I am called every 5 seconds")
}
Do not forget call invalidate method
timer.invalidate()
GCD approach (will tend to drift a bit late over time)
func repeatMeWithGCD() {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) {
print("I am called every 5 seconds")
self.repeatMeWithGCD()//recursive call
}
}
Do not forget to create a return condition to prevent stackoverflow error