I see a couple posts referring to this issue where starting multiple timers might be the problem. However, I do not see myself starting more than one, maybe I am just missing something? I am very new at this. Right now I am trying to start the timer if the user is going faster than 8 mps, and keep it running until he has stopped long enough for the timer to run out. Currently the timer keeps counting down even after the conditions are met to invalidate it.
Thanks a ton for looking, the help here is always super appreciated
import UIKit
class ViewController: UIViewController, {
//Setup timer
var timer = Timer()
var seconds = 5
var timerRunning = false
//Timer countdown
#objc func timeoutPeriod() {
seconds -= 1
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if userLocation.speed > 8 && timerRunning == false {
//flip running to True and start timer
timerRunning = true
seconds = 10
Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(timeoutPeriod)), userInfo: nil, repeats: true)
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
let placemark: CLPlacemark = placemarks![0]
if let city = placemark.locality {
self.startingLocation = city
print("Starting Location: " + city)
}
})
} else if userLocation.speed > 8 && timerRunning == true {
seconds = 10
} else if seconds == 0 {
//end timer (hopefully)
self.timer.invalidate()
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
let placemark: CLPlacemark = placemarks![0]
if let city = placemark.locality {
self.currentDateString = self.dateFormatter.string(from: self.date)
self.endingLocation = city
print("Ending Location: " + city)
self.timerRunning = false
self.seconds = 10
}
})
}
}
}
The normal timer function is not getting invalidated even if it is made nil but the below function helped me to solve the purpose. Hope this helps
self.myTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { (timerValue) in
if self.myTimer != nil {
self.updateTimerLabel() // call the selector function here
}
})
Your problem lies in this part of your code.
Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(timeoutPeriod)), userInfo: nil, repeats: true)
You're creating a Timer but not setting it into the Timer variable you created var timer = Timer()
To fix this you just have to set your Timer variable correctly.
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(timeoutPeriod)), userInfo: nil, repeats: true)
Why in my case:
First at DidLoad() ran slowly and stopped at increasInt = 9.
Second time triggered by code to make it automatically: reset increasInt = -1 - it ran faster
and continues after increasInt > 11
func aniDrag(ani: Double){
dragTimer = Timer.scheduledTimer(timeInterval: ani, target: self, selector: #selector(updatedrag), userInfo: nil, repeats: true)
}
#objc func updatedrag() { // dauIndex
increasInt = increasInt + 1
if (increasInt > -1 ) && ( increasInt < 10 ) {
aniImage.image = UIImage(named: "ani" + allLessonArray[realnIndex] + String(increasInt))!
print(allLessonArray[realnIndex] + String(increasInt) )
}
if increasInt == 10 {
if dragTimer != nil {
print("Timer Stop !!!!!")
dragTimer.invalidate()}
}
if increasInt > 11 { print(increasInt)}
}
//test aniDrag(ani: 0.5)
Related
I am getting a problem with Swift timer. Timer are not working properly for old iPhone models like iPhone 6. When I printed out time, timer counts every 2 second as 1 second. But if i change withTimeInterval as 0.05, it works a bit better. But still is not work as real life time. Here is my code. Can anyone help me ?
weak var timer: Timer?
var startTime : TimeInterval!
var elapsingTime: Double = 0.0
func configureTimer(totalSecond: Double) {
startTime = Date().timeIntervalSinceReferenceDate
self.invalidateTimer()
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 0.02,target: self,selector: #selector(self.advanceTimer(timer:)),userInfo: nil,repeats: true)
}
}
#objc func advanceTimer(timer: Timer) {
elapsingTime += 0.02
self.questionView.configureProgressBar(totalTime: Double(self.totalSecond), elapsingTime: elapsingTime)
self.isTimeExpired = false
self.elapsingTime = Date().timeIntervalSinceReferenceDate - self.startTime
if Int(elapsingTime) == Int(totalSecond) {
self.timer!.invalidate()
self.isTimeExpired = true
self.userAnswerIndex = -1
self.sendAnswer(index: self.userAnswerIndex, isTimeExpired: self.isTimeExpired)
}
}
Timers are not very precise. There precisions is about 0.05 seconds I think. And if a process require a lot of power, your timer will be even more slowed down. The solution can be to save the time when you start your timer and that each time your timer fire, you do a mathematical operation to know how many time passed :
class YourClass {
var startTime : TimeInterval!
//...
func configureTimer() {
startTime = Date().timeIntervalSinceReferenceDate //You add this line
elapsingTime = 0.0
timer?.invalidate()
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { [weak self] timer in
guard let self = self else { return }
self.elapsingTime = Date().timeIntervalSinceReferenceDate - self.startTime //You change this one
self.questionView.configureProgressBar(totalTime: self.totalTime, elapsingTime: self.elapsingTime)
self.questionView.videoQuestionPlayer.player?.play()
if Int(self.elapsingTime) == Int(self.totalTime) {
self.timer!.invalidate()
self.isTimeExpired = true
self.sendAnswer(index: -1)
}
})
}
}
}
I would try setting it up like this.
var myTimer = Timer()
var counter = 0.0
myTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(yourTargetFunctionHere), userInfo: nil, repeats: true)
#objc func yourTargetFunctionHere() {
//in here is where you do all your code work
counter += 0.1
if counter == yourElapsedTime {
//when your target time is hit do something here
}
if counter == finishedTime {
self.timer.invalidate()
self.counter = 0
}
}
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
Yesterday, I've ask because I have a problem with my timer in background, I've found a solution to make it works but now, I got a problem when my application enter in background.
Run in background:
backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(self.backgroundTaskIdentifier!)
})
This is my timer:
let interval = 0.01
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector:#selector(ViewController.updateTimer), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
This is my updateTimer function:
func updateTimer () {
var j = 0
for _ in rows {
if (rows[j]["Playing"] as! Bool == true ) {
rows[j]["time"] = (rows[j]["time"] as! Double + interval) as AnyObject
rows[j]["lastTime"] = (rows[j]["lastTime"] as! Double + interval) as AnyObject
}
if (rows[j]["lastTime"] as! Double > 60.0) {
min[j] += 1
rows[j]["lastTime"] = 0.00 as AnyObject
}
j += 1
}
}
In my applicationDidEnterBackground method I call this function:
func backgroundTimer() {
print("background") // This Print works fine
timer.invalidate() // Never works
interval = 1.00 // Crash when interval change from 0.01 to 1.00
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector:#selector(ViewController.updateTimer), userInfo: nil, repeats: true)
}
And this is my output when my application enter in background:
The Double is interval.
In Info.plist I add : Application does not run in background : NO.
Let me know what i'm doing wrong?
EDIT:
Initialization of rows in viewDidLoad method
let i: [String : AnyObject] = ["time": time as AnyObject, "Playing": false as AnyObject, "lastTime": 0.00 as AnyObject, "lapNumber": 0 as AnyObject, "min": 0 as AnyObject]
rows.append(i as [String : AnyObject])
Your basic problem seems to be the use of AnyObject for things that are not really objects. It causes the as! Bool to fail.
Here's a playground snippet that gets back a stored Bool value by allowing simple values in the dictionary.
var rows: [[String : Any]] = []
let i: [String : Any] = ["time": time, "Playing": false, "lastTime": 0.00, "lapNumber": 0, "min": 0]
rows.append(i)
let playing = rows[0]["Playing"]
if let playing = playing as? Bool {
print("Bool")
} else {
print("Something else \(String(describing: playing))")
}
I found solution for this problem.
Using NSNotification.
override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector:#selector(ViewController.backgroundTimer), name:NSNotification.Name.UIApplicationDidEnterBackground, object: nil)
NotificationCenter.default.addObserver(self, selector:#selector(ViewController.backToForeground), name:NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
}
func backgroundTimer(notification : NSNotification) {
self.timer.invalidate()
interval = 1.00
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector:#selector(ViewController.updateTimer), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
}
func backToForeground(notification: NSNotification) {
self.timer.invalidate()
interval = 0.01
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector:#selector(ViewController.updateTimer), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
}
Don't forgot to add the backgroundTaskIdentifier.
I suppose you are using the AppDelegate mainVC method that create a copy of your main. Thats why your timer are never invalidate in your ViewController. It is invalidate in your copy.
Check out this answer: Pausing timer when app is in background state Swift
I'm trying to make a function that activates every one second, which adds 1 to a variable (texttimer) and adds a letter to a string (typedStory), which is displayed on a label (storyLabel).
Everything worked fine until I added an argument (finalStory) to the function. Now I'm getting an an error:
-[__NSCFTimer substringToIndex:]: unrecognized selector sent to instance
Here's the code I have:
func textTypeWelcome(finalStory: NSString){
var newTimer2 = Timer.scheduledTimer(timeInterval: 0.05, target: self, selector: #selector(GameScene.textTypeWelcome), userInfo: finalStory, repeats: false)
texttimer += 1
typedStory = finalStory.substring(to: texttimer)
storyLabel.text = typedStory
}
The code works, however doesn't do what I want if I remove the argument and put userInfo to nil.
Anyone know of a solution? Thanks!
No, that cannot work, the action method takes the timer as parameter – what the error message tells you – and you get finalStory from the userInfo parameter.
func textTypeWelcome(timer: Timer) {
let finalStory = timer.userInfo as! String
var newTimer2 = Timer.scheduledTimer(timeInterval: 0.05, target: self, selector: #selector(GameScene.textTypeWelcome), userInfo: finalStory, repeats: false)
texttimer += 1
typedStory = finalStory.substring(to: texttimer)
storyLabel.text = typedStory
}
Try this out in a Playground
class Foo : NSObject {
let string = "Hello World"
var counter = 1
var typedStory = ""
var timer = Timer()
override init() {
super.init()
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
print("timer started")
}
func timerFired(timer : Timer) {
typedStory = string.substring(to: string.index(string.startIndex, offsetBy: counter))
print(typedStory)
if typedStory == string {
timer.invalidate()
print("timer stopped")
} else {
counter += 1
}
}
}
let foo = Foo()
You have to add the lines
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
to allow asynchronous API
I've been searching for a solution to pause my SpriteKit game when the user "tabs down" the game. So far I found a solution where you use SKAction's instead of NSTimer's, this works as long as the time between actions stays the same. However, my NSTimer's changes in speed. So I need to find another solution.
I have a bunch of NSTimer's located in GameScene -> didMoveToView
NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: Selector("SpawnBullets"), userInfo: nil, repeats: true)
NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("SpawnMeteors"), userInfo: nil, repeats: true)
NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: Selector("onTimer:"), userInfo: nil, repeats: true)
Now, how would I simply pause them when the app enters background?
EDIT: Added my timeInterval increase-speed-function
func onTimer(timer: NSTimer) {
var goodTimes = time / 20
if (goodTimes > 1.8){
goodTimes = 1.8
}
timer.fireDate = timer.fireDate.dateByAddingTimeInterval(timeInterval - goodTimes)
self.runAction(SKAction.sequence([SKAction.runBlock(SpawnRocks), SKAction.waitForDuration(goodTimes / 2), SKAction.runBlock(SpawnPowerUp)]))
}
Pausing of NSTimer is not a native feature in objective-c or swift. To combat this, you need to create an extension, which I happen to have created and will share for you. This will work for both OS X and iOS
import Foundation
import ObjectiveC
#if os(iOS)
import UIKit
#else
import AppKit
#endif
private var pauseStartKey:UInt8 = 0;
private var previousFireDateKey:UInt8 = 0;
extension NSTimer
{
private var pauseStart: NSDate!{
get{
return objc_getAssociatedObject(self, &pauseStartKey) as? NSDate;
}
set(newValue)
{
objc_setAssociatedObject(self, &pauseStartKey,newValue,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN);
}
}
private var previousFireDate: NSDate!{
get{
return objc_getAssociatedObject(self, &previousFireDateKey) as? NSDate;
}
set(newValue)
{
objc_setAssociatedObject(self, &previousFireDateKey,newValue,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN);
}
}
func pause()
{
pauseStart = NSDate();
previousFireDate = self.fireDate;
self.fireDate = NSDate.distantFuture() ;
}
func resume()
{
if(pauseStart != nil)
{
let pauseTime = -1 * pauseStart.timeIntervalSinceNow;
let date = NSDate(timeInterval:pauseTime, sinceDate:previousFireDate );
self.fireDate = date;
}
}
}
Then when you need to use it, simply call timer.pause() and timer.resume() You of course have to keep track of your timers in your gamescene object to do this, so make sure that timer is a variable accessible for the entire class, and when making your timer, you do timer = NSTimer.schedule...
At the beginning of your class:
var spawnBulletsTimer : NSTimer?;
var spawnMeteorsTimer : NSTimer?;
var onTimer: NSTimer?;
When creating the timers:
spawnBulletsTimer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: Selector("SpawnBullets"), userInfo: nil, repeats: true)
spawnMeteorsTimer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("SpawnMeteors"), userInfo: nil, repeats: true)
onTimer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: Selector("onTimer:"), userInfo: nil, repeats: true)
Then when you need to pause:
onTimer?.pause()
spawnBulletsTimer?.pause()
spawnMeteorTimer?.pause()
Then when you need to resume:
onTimer?.resume()
spawnBulletsTimer?.resume()
spawnMeteorTimer?.resume()
Thanks #KnightOfDragon code, here's a more modern swift version:
import Foundation
private var pauseStartKey:UInt8 = 0;
private var previousFireDateKey:UInt8 = 0;
extension Timer{
private var pauseStart: Date!{
get{
return objc_getAssociatedObject(self, &pauseStartKey) as? Date
}
set(newValue){
objc_setAssociatedObject(self, &pauseStartKey,newValue,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
private var previousFireDate: Date! {
get{
return objc_getAssociatedObject(self, &previousFireDateKey) as? Date
}
set(newValue){
objc_setAssociatedObject(self, &previousFireDateKey,newValue,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
func pause() {
pauseStart = Date()
previousFireDate = fireDate
fireDate = Date.distantFuture
}
func resume() {
if pauseStart != nil {
let pauseTime = -1 * pauseStart.timeIntervalSinceNow;
let date = Date(timeInterval: pauseTime, since: previousFireDate)
fireDate = date;
}
}
}