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.
Related
I'm still pretty new to coding and Swift. So bear with me.
Problem Statement : I've got a stopwatch style app that has two concurrent timers start at the same time and display in a mm:ss.SS format, but one is designed to reset to 0 at specific intervals automatically while the other keeps going and tracks total time.
Similar to a "lap" function but it does it automatically. The problem I've encountered is that occasionally the timers aren't perfectly synced up when the user pauses the timers. Since the reset happens at an exact second, both timers should have identical hundredths of a second, while the seconds and minutes will obviously be different. But sometimes the hundredths will be off by .01 or more.
Now, I know Timer isn't designed to be perfectly accurate, and in practice on my app this isn't even a huge deal. My timer doesn't even need to be accurate to the hundredth of a second, and while running it's not noticeably off at all, only while paused. I could display fewer decimal places or none at all, but I prefer the style of showing the hundredths since it fits in well with the stock timer app style.
So if there's a way to make this work, I'd like to keep it.
Screenshot : screenshot
What I tried :
#IBAction func playPauseTapped(_ sender: Any) {
if timerState == .new {
//start new timer
startCurrentTimer()
startTotalTimer()
currentStartTime = Date.timeIntervalSinceReferenceDate
totalStartTime = Date.timeIntervalSinceReferenceDate
timerState = .running
//some ui updates
} else if timerState == .running {
//pause timer
totalTimer.invalidate()
currentTimer.invalidate()
timerState = .paused
pausedTime = Date()
//other ui updates
} else if timerState == .paused {
//resume paused timer
let pausedInterval = Date().timeIntervalSince(pausedTime!)
pausedIntervals.append(pausedInterval)
pausedIntervalsCurrent.append(pausedInterval)
pausedTime = nil
startCurrentTimer()
startTotalTimer()
timerState = .running
//other ui updates
}
}
func startTotalTimer() {
totalTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(runTotalTimer), userInfo: nil, repeats: true)
}
func startCurrentTimer() {
currentTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(runCurrentTimer), userInfo: nil, repeats: true)
}
func resetCurrentTimer() {
currentTimer.invalidate()
currentStartTime = Date.timeIntervalSinceReferenceDate
pausedIntervalsCurrent.removeAll()
startCurrentTimer()
}
#objc func runCurrentTimer() {
let currentTime = Date.timeIntervalSinceReferenceDate
//calculate total paused time
var pausedSeconds = pausedIntervalsCurrent.reduce(0) { $0 + $1 }
if let pausedTime = pausedTime {
pausedSeconds += Date().timeIntervalSince(pausedTime)
}
let currentElapsedTime: TimeInterval = currentTime - currentStartTime - pausedSeconds
currentStepTimeLabel.text = format(time: currentElapsedTime)
if currentElapsedTime >= recipeInterval {
if recipeIndex < recipeTime.count - 1 {
recipeIndex += 1
//ui updates
//reset timer to 0
resetCurrentTimer()
} else {
//last step
currentTimer.invalidate()
}
}
}
#objc func runTotalTimer() {
let currentTime = Date.timeIntervalSinceReferenceDate
//calculate total paused time
var pausedSeconds = pausedIntervals.reduce(0) { $0 + $1 }
if let pausedTime = pausedTime {
pausedSeconds += Date().timeIntervalSince(pausedTime)
}
let totalElapsedTime: TimeInterval = currentTime - totalStartTime - pausedSeconds
totalTimeLabel.text = format(time: totalElapsedTime)
if totalElapsedTime >= recipeTotalTime {
totalTimer.invalidate()
currentTimer.invalidate()
//ui updates
}
}
func format(time: TimeInterval) -> String {
//formats TimeInterval into mm:ss.SS
let formater = DateFormatter()
formater.dateFormat = "mm:ss.SS"
let date = Date(timeIntervalSinceReferenceDate: time)
return formater.string(from: date)
}
You should use a single timer. And when you need a reset to zero, save the current time to a variable.
When presenting the time in the UI, calculate the difference between the running total timer, and the time you saved previously.
New guy here teaching myself Swift. Building my first personal app and have hit a wall after several searches on here, youtube, and google. This is my first time posting a question (as I've been able to find my other answers on here).
I'm having issues with my timer updating on the UILabel. I've managed to find code on older versions of swift that get the timer to run and count down. I then figured out how to break the seconds down to minutes and seconds.
But what I find is when I run the app, the timer shows "30:0" (a different issue I need to figure out) and never counts down. When I leave the page in the simulator and come back, it's only then that the UILabel updates.
I know viewdidload only loads upon the first moment the page opens. I'm having a hard time figuring out how to get the UILabel to update every time a second changes. I'm not sure what code to implement.
Thank you so much!
import UIKit
var timer = Timer()
var timerDuration: Int = 1800
// This converts my timeDuration from seconds to minutes and seconds.
func secondsToHoursMinutesSeconds (seconds : Int) -> (h: Int, m : Int, s : Int) {
return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)
}
class lyricWriteViewController: UIViewController {
//This takes the function to convert minutes and seconds and accepts an input, which I've chosen the variable timeDuration (which is currently 1800 seconds.
var theTimer = (h: 0, m: 0, s: 0)
#IBOutlet weak var countdownTimer: UILabel!
#IBOutlet weak var randomLyric: UILabel!
#IBOutlet weak var titleInput: UITextField!
#IBOutlet weak var lyricInput: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
//This line takes a random array number and shows it on the textlabel.
randomLyric.text = oneLiner
theTimer = secondsToHoursMinutesSeconds(seconds: timerDuration)
//This is the code the does the counting down
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(lyricWriteViewController.counter), userInfo: nil, repeats: true)
}
#objc func counter() {
timerDuration -= 1
// Below is the timer view I've created. It has converted seconds to minutes and seconds but the screen won't refresh. Also, when the seconds number hits zero, it does "0" instead of "00".
let displayTimer = "\(theTimer.m) : \(theTimer.s)"
countdownTimer.text = String(displayTimer)
//When the timer hits 0, it stops working so that it doesn't go into negative numbers
if timerDuration == 0 {
timer.invalidate()
}
}
func submitlyricsButton(_ sender: UIButton) {
//I will eventually tie this to a completed lyric tableview.
}
}
var timer: Timer?
var totalTime = 120
private func startOtpTimer() {
self.totalTime = 120
self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
}
#objc func updateTimer() {
print(self.totalTime)
self.lblTimer.text = self.timeFormatted(self.totalTime) // will show timer
if totalTime != 0 {
totalTime -= 1 // decrease counter timer
}
else {
if let timer = self.timer {
timer.invalidate()
self.timer = nil
}
}
}
func timeFormatted(_ totalSeconds: Int) -> String {
let seconds: Int = totalSeconds % 60
let minutes: Int = (totalSeconds / 60) % 60
return String(format: "%02d:%02d", minutes, seconds)
}
It is because you are not updating the theTimer value. As viewDidLoad() is called once it is not working fine, you need to update theTimer value after deducting 1 from it.
So move this line :
theTimer = secondsToHoursMinutesSeconds(seconds: timerDuration)
in counter() funtion after timerDuration -= 1. So your function should look like this :
#objc func counter() {
timerDuration -= 1
if timerDuration == 0 {
timer.invalidate()
} else {
theTimer = secondsToHoursMinutesSeconds(seconds: timerDuration)
let displayTimer = "\(theTimer.m) : \(theTimer.s)"
countdownTimer.text = String(displayTimer)
}
}
Also move all of this inside controller:
var timer = Timer()
var timerDuration: Int = 1800
// This converts my timeDuration from seconds to minutes and seconds.
func secondsToHoursMinutesSeconds (seconds : Int) -> (h: Int, m : Int, s : Int){
return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)}
As timerDuration is global you will have to kill the app and run it again to see the timer working again.
Replace countdownTimer.text = String(displayTimer) with
DispatchQueue.main.async {
countdownTimer.text = String(displayTimer)
}
What I think is happening here is since countdownTimer.text = String(displayTimer) is not running on the main thread it is not updating immediately. It does however after a period of time (Like you said, when when you traverse the screen).
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.
When I start a timer, however, it seems to repeatedly run, but it won't decrease the counter. I'm trying to pass the count variable through the counter into the selector, but it seems that the counter resets each time, instead of continuely decreasing. I'm new to programming, so while I'm hoping it's something silly, I might have everything wrong organizationally... my code is:
func timerDidEnd(timer: NSTimer) {
var timeCount = timer.userInfo as! Double
timeCount -= timeInterval
if timeCount <= 0 { //test for target time reached.
print("Timer = 0")
timer.invalidate()
} else { //update the time on the clock if not reached
print("Timer Count: \(timeCount)")
}
extension ViewController: TimerTableViewCellDelegate {
func startTimer(indexPath: NSIndexPath!) {
print("timer \(indexPath.row) button started")
var currentTimer = baseArray[indexPath.row]
currentTimer.timeCount = Double((currentTimer.duration[0] * 60) + (currentTimer.duration[1]) + currentTimer.duration[2])
currentTimer.timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: "timerDidEnd:", userInfo: currentTimer.timeCount, repeats: true)
}
func stopTimer(indexPath: NSIndexPath!) {
let currentTimer = baseArray[indexPath.row]
print("Stop Timer")
currentTimer.timer.invalidate()
}
I figured it out- I updated my timer struct to include the proper time count, and this worked while passing in the indexPath as userInfo
I'm working on a timer app for iPhone. But when switching views and coming back to initial timer view,
the label is not updated. While I can see it's still running in the print log.
I have the code below in my viewDidLoad.
How can I start refreshing the label again when I enter the timer view again?
The other view is handled through Segue.
func updateTime() {
var currentTime = NSDate.timeIntervalSinceReferenceDate()
//Find the difference between current time and start time.
var elapsedTime: NSTimeInterval = currentTime - startTime
//calculate the minutes in elapsed time.
let minutes = UInt8(elapsedTime / 60.0)
elapsedTime -= (NSTimeInterval(minutes) * 60)
//calculate the seconds in elapsed time.
let seconds = UInt8(elapsedTime)
elapsedTime -= NSTimeInterval(seconds)
//find out the fraction of milliseconds to be displayed.
let fraction = UInt8(elapsedTime * 100)
//add the leading zero for minutes, seconds and millseconds and store them as string constants
let strMinutes = minutes > 9 ? String(minutes):"0" + String(minutes)
let strSeconds = seconds > 9 ? String(seconds):"0" + String(seconds)
println("----------")
println("currentTime")
println (currentTime)
println("elapsedTime")
println (elapsedTime)
println("extraTime")
println (extraTime)
println("summed")
println (summed)
//concatenate minuets, seconds and milliseconds as assign it to the UILabel
displayTimeLabel.text = "\(strMinutes):\(strSeconds)"
}
#IBAction func start(sender: AnyObject) {
if (!timer.valid) {
let aSelector : Selector = "updateTime"
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate()
}
}
#IBAction func stop(sender: AnyObject) {
timer.invalidate()
}
I was having the exact same problem in a tab view application and solved it using the NSNotification Center. To make it work in your case, you could make a separate function just for updating the text.
func updateText(notification: NSNotification) {
displayTimeLabel.text = "\(strMinutes):\(strSeconds)"
}
Then inside your "updateTime" function, where you had the line I took out, replace it with a postNotifiction:
NSNotificationCenter.defaultCenter().postNotificationName("UpdateTimer", object: nil)
Then put the observer inside a ViewDidAppear function in the View Controller where the text should be updated:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(false)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateText:", name: "UpdateTimer", object: nil)
}
With the observer in viewDidAppear, the updateText function always gets called, and the text is updated even when you switch views and come back.