Swift Timer not working properly for old iPhone models - swift

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
}
}

Related

How can I set up timer in Swift?

I have a pickerController set the hours, minutes and seconds but I don't know how to set up my timer, this is the Code I have, but it doesn't work, how can I change that? Thank's in advance
func startTimer() {
if timer.isValid == true || timeHours == 0 || timeMinutes == 0 || timeSeconds == 0 {
//do nothing
} else {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(onTimerFires), userInfo: nil, repeats: true)
}
}
#objc func onTimerFires() {
timeHours -= 1
timeMinuteLabel.text = timeFunc()
if timeSeconds == 0 {
timer.invalidate()
}
}
func timeFunc() -> String {
let hours = Int(optionalValuePickerView.selectedRow(inComponent: 0))
let minutes = Int(optionalValuePickerView.selectedRow(inComponent: 1))
let seconds = Int(optionalValuePickerView.selectedRow(inComponent: 2))
return String(format: "%02i:%02i:%02i", hours, minutes, seconds)
}
There are a few issues here:
Every time onTimerFires is called, it is decrementing timeHours (which does not appear to be set or used anywhere) and then calls timeFunc, which just grabs values from the picker (which are not changing anywhere) to build the string.
This pattern of decrementing a counter in a timer makes a big assumption, namely that the timer is invoked exactly at the right time and that it will not ever miss a timer call. That is not a prudent assumption, unfortunately.
I would advise against the Selector based timer, as that introduces a strong reference cycle with the target. The block-based rendition with weak references is easier to avoid these sorts of cycles.
I would suggest a different pattern, namely that you save the time to which you are counting down:
var countDownDate: Date!
func updateCountDownDate() {
let components = DateComponents(hour: pickerView.selectedRow(inComponent: 0),
minute: pickerView.selectedRow(inComponent: 1),
second: pickerView.selectedRow(inComponent: 2))
countDownDate = Calendar.current.date(byAdding: components, to: Date())
}
Then your timer handler can calculate the amount of elapsed time to the target count down date/time:
func handleTimer(_ timer: Timer) {
let remaining = countDownDate.timeIntervalSince(Date())
guard remaining >= 0 else {
timer.invalidate()
return
}
label.text = timeString(from: remaining)
}
When you start the timer, you calculate the countDownDate and schedule the timer:
weak var timer: Timer?
func startTimer() {
timer?.invalidate() // if one was already running, cancel it
updateCountDownDate()
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
guard let self = self else {
timer.invalidate()
return
}
self.handleTimer(timer)
}
timer?.fire()
}
And, for what is worth, when preparing the string, you certainly can determine the date components between to dates (i.e., now and your target date/time), but you can also use a DateComponentsFormatter do this for you:
let formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second]
formatter.zeroFormattingBehavior = .pad
return formatter
}()
func timeString(from interval: TimeInterval) -> String? {
return formatter.string(from: interval)
}

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

Timer will not Invalidate swift 4

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)

Timer counts instantly instead of respecting interval

Im trying to make a countdown timer. everything works fine except that my timer does not count at regular intervals (1sec); instead it counts all the way down instantly giving me 0 every time. did a lot of search without luck. All examples I could find show similar timeInterval parameter.
var timer = Timer()
var remainingTime = 120
#objc func timerCount () {
if remainingTime > 0 {
while remainingTime > 0 {
remainingTime -= 1
timerLabel.text = String(remainingTime)
print(remainingTime)
}
} else {
timer.invalidate()
}
}
#IBAction func pauseButton(_ sender: Any) {
timer.invalidate()
}
#IBAction func playButton(_ sender: Any) {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerCount), userInfo: nil, repeats: true)
}
The reason your code is not working is that you have put an unnecessary while loop in your timerCount() method. You don't need to do this. Your timer will fire this method after each time interval. At very first call this while loop make your remainingTime to 0. This is why you are instantly getting 0 every time.
You just need to remove that while loop.
Can You try like this
var timer = Timer()
var remainingTime = 120
#objc func timerCount () {
let date = NSDate()
let nowdate = UserDefaults.standard.object(forKey: "NowDate") as! Date
let miniute = nowdate.timeIntervalSince(date as Date)
print(Int(miniute))
if (Int(miniute) == 0) {
timer.invalidate()
UserDefaults.standard.removeObject(forKey: "NowDate")
} else {
timerLabel.text = String(Int(miniute))
}
}
#IBAction func pauseButton(_ sender: Any) {
timer.invalidate()
UserDefaults.standard.removeObject(forKey: "NowDate")
}
#IBAction func playButton(_ sender: Any) {
let CurrentDate = NSDate()
let NowDate = CurrentDate.addingTimeInterval(TimeInterval(remainingTime))
UserDefaults.standard.set(NowDate, forKey: "NowDate")
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerCount), userInfo: nil, repeats: true)
}

2 decimal timer in swift

I am making a game that has a timer. I have the number increasing at intervals of 0.01 seconds but the number that comes up does not have the decimal point in it. How do I add this decimal point in? What I get after 1 second of the timer running is "100" instead of "1.00"
Here is the code I used:
//timer
{
var timer = NSTimer()
var counter = 0
countingLabel.text = String(counter)
timer = NSTimer.scheduledTimerWithTimeInterval(1, target:self, selector: Selector("updateCounter"), userInfo: nil, repeats: true)
}
//------timer update-----
func updateCounter() {
countingLabel.text = String(counter++)
if score.text == "10" {
timer.invalidate()
}
}
Try replacing
countingLabel.text = String(counter++)
with
counter++
countingLabel.text = String(Double(counter)/100)