I need multiple countdown timers with different periods following each other. How can I stop one and start another in the same activity and the same label.
I have this code for now:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var countDownLabel: UILabel!
var count = Int()
var timer = Timer()
var timerIndex = Int()
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
var timerHasFinishedRunning: Bool = false
func update() {
let minutes = String(count / 60)
let seconds = String(count % 60)
if count == 0 {
if timerIndex == 0 { count = 12
}
else if timerIndex == 1 {
count = 11
}
else if timerIndex == 2 {
count = 10
}
else if timerIndex == 3 {
count = 9
}
else {
timer.invalidate()
countDownLabel.text = "The timer has finished running!"
timerHasFinishedRunning = true
}
if timerHasFinishedRunning == false{
let minutes = String(count / 60)
let seconds = String(count % 60)
countDownLabel.text = minutes + ":" + seconds
timer.invalidate()
timerIndex += 1
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
}
}
else {
if timerIndex == 0{
count -= 1
let minutes = String(count / 60)
let seconds = String(count % 60)
countDownLabel.text = minutes + ":" + seconds
}
else if timerIndex == 1{
count -= 1
let minutes = String(count / 60)
let seconds = String(count % 60)
countDownLabel.text = minutes + ":" + seconds
}
else if timerIndex == 2{
count -= 1
let minutes = String(count / 60)
let seconds = String(count % 60)
countDownLabel.text = minutes + ":" + seconds
}
else if timerIndex == 3{
count -= 1
let minutes = String(count / 60)
let seconds = String(count % 60)
countDownLabel.text = minutes + ":" + seconds
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Initial Answer:
Replace your update() function to this, if you want the format to be M:S:
var timerHasFinishedRunning: Bool = false
func update() {
if count == 0{
if timerIndex == 0{ count = 200 // or the desired value for the second timer
}
else if timerIndex == 1{
count = 40 // or the desired value for the third timer
}
else {
timer.invalidate()
countDownLabel.text = "The timer has finished running!"
timerHasFinishedRunning = true
}
if timerHasFinishedRunning == false{
let minutes = String(count / 60)
let seconds = String(count % 60)
countDownLabel.text = minutes + ":" + seconds
timer.invalidate()
timerIndex += 1
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
}
}
else {
if timerIndex == 0{
count -= 1
let minutes = String(count / 60)
let seconds = String(count % 60)
countDownLabel.text = minutes + ":" + seconds
}
else if timerIndex == 1{
count -= 1
let minutes = String(count / 60)
let seconds = String(count % 60)
countDownLabel.text = minutes + ":" + seconds
}
else if timerIndex == 2{
count -= 1
let minutes = String(count / 60)
let seconds = String(count % 60)
countDownLabel.text = minutes + ":" + seconds
}
}
}
EDIT:
Replace your code to this, and add your new values! Format: MM:SS:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var countDownLabel: UILabel!
var count = Int()
var timer = Timer()
var timerIndex = 0
var timerHasFinishedRunning: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
count = 5
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
}
func update() {
if count == 0{
if timerIndex == 0{ count = 3 // or the desired value for the second timer
}
else if timerIndex == 1{
count = 2 // or the desired value for the third timer
}
else {
timer.invalidate()
countDownLabel.text = "The timer has finished running!"
timerHasFinishedRunning = true
}
if timerHasFinishedRunning == false{
let minutes = Int(count / 60)
let seconds = Int(count % 60)
if minutes > 9{
if seconds >= 10{
countDownLabel.text = "\(minutes)" + ":" + "\(seconds)"
}
else if seconds < 10{
countDownLabel.text = "\(minutes)" + ":" + "0\(seconds)"
}
}
else if minutes<=9{
if seconds >= 10{
countDownLabel.text = "0\(minutes)" + ":" + "\(seconds)"
}
else if seconds < 10{
countDownLabel.text = "0\(minutes)" + ":" + "0\(seconds)"
}
}
timer.invalidate()
timerIndex += 1
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
}
}
else {
count -= 1
let minutes = Int(count / 60)
let seconds = Int(count % 60)
if minutes > 9{
if seconds >= 10{
countDownLabel.text = "\(minutes)" + ":" + "\(seconds)"
}
else if seconds < 10{
countDownLabel.text = "\(minutes)" + ":" + "0\(seconds)"
}
}
else if minutes<=9{
if seconds >= 10{
countDownLabel.text = "0\(minutes)" + ":" + "\(seconds)"
}
else if seconds < 10{
countDownLabel.text = "0\(minutes)" + ":" + "0\(seconds)"
}
}
}
}
}
I will update the answer once again, if I will figure out a way to fix your issue.
Hope this helps!
SIMULATOR:
try this..just change according to your requirement
var count = Int()
var timer = NSTimer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a
printSecondsToHoursMinutesSeconds(100)
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
}
func update() {
count = 1 + count
if(count != 0) {
printSecondsToHoursMinutesSeconds(count)
} else {
timer.invalidate()
}
}
func printSecondsToHoursMinutesSeconds (seconds:Int) -> () {
let (h, m, s) = secondsToHoursMinutesSeconds (Double(seconds))
print ("\(h) Hours, \(m) Minutes, \(s) Seconds")
}
func secondsToHoursMinutesSeconds (seconds : Double) -> (Double, Double, Double) {
let (hr, minf) = modf (seconds / 3600)
let (min, secf) = modf (60 * minf)
return (hr, min, 60 * secf)
}
Related
I have a countdown Timer that shows seconds and milliseconds. The user can start/stop recording multiple times until the timer hits zero. The user can also delete a previous recording at which point I have to re-add that deleted time back into the initial 20 secs. There are 2 issues.
The first issue is when the timer is stopped, the remaining time that shows on the timer label doesn't match the time culmination of the recordings. From my understanding this might be a RunLoop issue and I don't think there is anything that I can do about the inaccuracies.
let initialTime = 20.0
var cumulativeTimeForAllAssests = 0.0
for asset in arrOfAssets {
let assetDuration = CMTimeGetSeconds(asset.duration)
print("assetDuration: ", assetDuration)
cumulativeTimeForAllAssests += assetDuration
}
print("\ncumulativeTimeForAllAssests: ", cumulativeTimeForAllAssests)
After starting/stopping 5 times, the remaining time on the timer label says 16.5 but the culmination of the assets time is 4.196666.... The timer label should say 15.8, it's 0.7 milli off. The more I start/stop the recording, the more inaccurate/further off the culmination time - the initial time and the timer label time is.
assetDuration: 0.7666666666666667
assetDuration: 0.9666666666666667
assetDuration: 0.7983333333333333
assetDuration: 0.7333333333333333
assetDuration: 0.9316666666666666
cumulativeTimeForAllAssests: 4.196666666666667
The second issue is because I'm using seconds and milliseconds in my timerLabel, when I add re-add the subtracted time back in via deleteAssetAndUpdateTimer(...), I use the parts of modf() to update the seconds and milliseconds. I couldn't think of another way to update the timer. I know there has to be a more accurate way to do it.
Timer code:
weak var timer: Timer?
var seconds = 20
var milliseconds = 0
let initialTime = 20.0
func startTimer() {
invalidateTimer()
if seconds == Int(initalTime) && milliseconds == 0 {
timerIsRunning()
}
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [weak self] _ in
self?.timerIsRunning()
})
}
func timerIsRunning() {
updateTimerLabel()
if milliseconds == 0 {
seconds -= 1
}
milliseconds -= 1
if milliseconds < 0 {
milliseconds = 9
}
if seconds == 0 && milliseconds == 0 {
invalidateTimer()
updateTimerLabel()
}
}
func invalidateTimer() {
timer?.invalidate()
timer = nil
}
func updateTimerLabel() {
let milisecStr = "\(milliseconds)"
let secondsStr = seconds > 9 ? "\(seconds)" : "0\(seconds)"
timerLabel.text = "\(secondsStr).\(milisecStr)"
}
Delete asset and update timer code:
// the timer is stopped when this is called
func deleteAssetAndUpdateTimer(_ assetToDelete: AVURLAsset) {
var cumulativeTimeForAllAssests = 0.0
for asset in arrOfAssets {
let assetDuration = CMTimeGetSeconds(asset.duration)
cumulativeTimeForAllAssests += assetDuration
}
let timeFromAssetToDelete = CMTimeGetSeconds(assetToDelete.duration)
let remainingTime = self.initialTime - cumulativeTimeForAllAssests
let updatedTime = remainingTime + timeFromAssetToDelete
let mod = modf(updatedTime)
self.seconds = Int(mod.0)
self.milliseconds = Int(mod.1 * 10)
updateTimerLabel()
// remove assetToDelete from array
}
The big issue here was I was using a Timer to countdown which was incorrect. Following #LeoDabus' comments, I instead used CACurrentMediaTime():
let timerLabel = UILabel()
let maxRecordingTime = 30.0
lazy var elapsedTime = maxRecordingTime
var startTime: CFTimeInterval?
var endTime: CFTimeInterval?
weak var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
updateTimerLabel(with: Int(maxRecordingTime))
}
#IBAction func recordButtonPressed(_ sender: UIButton) {
if startTime == nil {
startTimer()
} else {
stopTimer(updateElapsed: true)
}
}
func startTimer() {
if elapsedTime == 0 { return }
stopTimer()
startTime = CACurrentMediaTime()
endTime = startTime! + elapsedTime
print("startTime: \(startTime!) | endTime: \(endTime!)")
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { [weak self] _ in
self?.timerIsRunning()
}
}
func timerIsRunning() {
guard let startTime = startTime, let endTime = endTime else { return }
let currentTime = CACurrentMediaTime()
let remainingTime = currentTime - startTime
print("%2d %.3lf", elapsedTime, remainingTime)
if currentTime >= endTime {
print("stopped at - currentTime: \(currentTime) | endTime: \(endTime)")
stopTimer(updateElapsed: true, currentTime: currentTime)
return
}
let countDownTime: Double = elapsedTime - remainingTime
let seconds = Int(countDownTime)
updateTimerLabel(with: seconds)
}
func updateTimerLabel(with seconds: Int) {
let secondsStr = seconds > 9 ? "\(seconds)" : "0\(seconds)"
timerLabel.text = secondsStr
}
func stopTimer(updateElapsed: Bool = false, currentTime: Double? = nil) {
timer?.invalidate()
timer = nil
if updateElapsed {
updateElapsedTime(using: currentTime)
}
startTime = nil
endTime = nil
}
func updateElapsedTime(using currentTime: Double? = nil) {
guard let startTime = startTime else { return }
var timeNow = CACurrentMediaTime()
if let currentTime = currentTime {
timeNow = currentTime
}
var updatedTime = elapsedTime - (timeNow - startTime)
if updatedTime < 0 {
updatedTime = 0
}
elapsedTime = updatedTime
}
func resetElapsedTime() { // This is for a resetButton not shown here
elapsedTime = maxRecordingTime
}
I want to implement timer logic, when 5 min gets complete then my Tableview reload and its remove that particular index, I have tried not gets works, and timer get fast
//Timer ACtion Method
#objc func timerAction() {
if seconds>0 {
seconds-=1
minutes = String(format:"%02i",(seconds / 60))
seconds1 = String(format:"%02i",(seconds % 60))
print(minutes! + ":" + seconds1!)
self.lblMin.text = minutes!
self.lblSec.text = seconds1!
} else {
minutes = String(seconds / 60)
seconds1 = String(seconds % 60)
if minutes == "0" && seconds1 == "0" {
timer.invalidate()
btnReject.isUserInteractionEnabled = false
btnAccept.isUserInteractionEnabled = false
// TBVC.InstancePending.arrPending.remove(at: intValue!)
//tblData?.deleteRows(at: [IndexPath(row: intValue!, section: 1)], with: .automatic)
// TBVC.InstancePending.getTableBooking(strStatus: "0")
// TBVC.InstancePending.strTap = "Pending"
// TBVC.InstancePending.segment.selectedSegmentIndex = 0
// tblData?.reloadData()
}
}
}
Set Timer value nil, And check when API called then the timer will not pass any selector method
#objc func timerAction() {
if seconds>0 {
seconds-=1
minutes = String(format:"%02i",(seconds / 60))
seconds1 = String(format:"%02i",(seconds % 60))
print(minutes! + ":" + seconds1!)
self.lblMin.text = minutes!
self.lblSec.text = seconds1!
} else {
minutes = String(seconds / 60)
seconds1 = String(seconds % 60)
if minutes == "0" && seconds1 == "0" {
timer.invalidate()
timer = nil
btnReject.isUserInteractionEnabled = false
btnAccept.isUserInteractionEnabled = false
// TBVC.InstancePending.arrPending.remove(at: intValue!)
//tblData?.deleteRows(at: [IndexPath(row: intValue!, section: 1)], with: .automatic)
// TBVC.InstancePending.getTableBooking(strStatus: "0")
// TBVC.InstancePending.strTap = "Pending"
// TBVC.InstancePending.segment.selectedSegmentIndex = 0
// tblData?.reloadData()
}
}
}
=====================================
2nd method to implement timer:-
Initialize Variable
var timer:Timer?
var totalMinut:Int = 2
var totalSecond:Int = 120
var timeLeft = 120
Add timer function
func setupTimer() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(onTimerFires), userInfo: nil, repeats: true)
}
#objc func onTimerFires() {
var minutes: Int
var seconds: Int
if totalSecond == 1 {
timer?.invalidate()
timer = nil
}
totalSecond = totalSecond - 1
minutes = (totalSecond) / 60
seconds = (totalSecond) % 60
timerLabel.text = String(format: "%02d:%02d", minutes, seconds)
}
Call "setUpTimer" method where you have required. In my case, I have called it in the "viewDidLoad" method of a view controller
override func viewDidLoad() {
super.viewDidLoad()
setupTimer()
}
I have a countdown timer that shows milliseconds and a label that shows the time as it runs. When it stops at zero instead of it showing 00:00.0 it shows 00:01.0
I can't figure out where I'm going wrong:
weak var videoTimer: Timer?
var minutes = 2
var seconds = 0
var milliseconds = 0
func startTimer() {
videoTimer?.invalidate()
videoTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [weak self] _ in
self?.timerIsRunning()
})
RunLoop.current.add(videoTimer!, forMode: RunLoop.Mode.common)
}
func timerIsRunning() {
let millisecStr = "\(milliseconds)"
let secondsStr = seconds > 9 ? "\(seconds)" : "0\(seconds)"
let minutesStr = minutes > 9 ? "\(minutes)" : "0\(minutes)"
timerLabel.text = "\(minutesStr):\(secondsStr).\(millisecStr)"
if seconds == 0 {
if minutes != 0 {
minutes -= 1
}
}
if milliseconds == 0 {
seconds -= 1
}
if seconds < 0 {
seconds = 59
}
if minutes == 0 && seconds == 0 && milliseconds == 0 {
print(minutes) // prints 0
print(seconds) // prints 0
print(milliseconds) // prints 0
videoTimer?.invalidate()
}
milliseconds -= 1
if milliseconds < 0 {
milliseconds = 9
}
}
Take your millisecond function above .. 00:01.0 means 10 milliseconds remaining ...
weak var videoTimer: Timer?
var minutes = 2
var seconds = 0
var milliseconds = 0
func startTimer() {
videoTimer?.invalidate()
videoTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [weak self] _ in
self?.timerIsRunning()
})
RunLoop.current.add(videoTimer!, forMode: RunLoop.Mode.common)
}
func timerIsRunning() {
showTimer()
if seconds == 0 {
if minutes != 0 {
minutes -= 1
}
}
if milliseconds == 0 {
seconds -= 1
}
if seconds < 0 {
seconds = 59
}
milliseconds -= 1
if milliseconds < 0 {
milliseconds = 9
}
if minutes == 0 && seconds == 0 && milliseconds == 0 {
print(minutes) // prints 0
print(seconds) // prints 0
print(milliseconds) // prints 0
showTimer()
videoTimer?.invalidate()
}
}
func showTimer() {
let millisecStr = "\(milliseconds)"
let secondsStr = seconds > 9 ? "\(seconds)" : "0\(seconds)"
let minutesStr = minutes > 9 ? "\(minutes)" : "0\(minutes)"
timerLabel.text = "\(minutesStr):\(secondsStr).\(millisecStr)"
}
It's because the value you assigned to millisecStr, secondsStr, minutesStr, timerLabel.text are not the latest value. You should assign them after calculating.
just try this.
weak var videoTimer: Timer?
var minutes = 2
var seconds = 0
var milliseconds = 0
func startTimer() {
videoTimer?.invalidate()
videoTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [weak self] _ in
self?.timerIsRunning()
})
RunLoop.current.add(videoTimer!, forMode: RunLoop.Mode.common)
}
func timerIsRunning() {
if seconds == 0 {
if minutes != 0 {
minutes -= 1
}
}
if milliseconds == 0 {
seconds -= 1
}
if seconds < 0 {
seconds = 59
}
let millisecStr = "\(milliseconds)"
let secondsStr = seconds > 9 ? "\(seconds)" : "0\(seconds)"
let minutesStr = minutes > 9 ? "\(minutes)" : "0\(minutes)"
timerLabel.text = "\(minutesStr):\(secondsStr).\(millisecStr)"
if minutes == 0 && seconds == 0 && milliseconds == 0 {
print(minutes) // prints 0
print(seconds) // prints 0
print(milliseconds) // prints 0
videoTimer?.invalidate()
}
milliseconds -= 1
if milliseconds < 0 {
milliseconds = 9
}
}
I am currently trying to increment a uistepper and change its value based on its current value. The uistepper is supposed to act as a time keeper. It increments by 5 minutes until it reaches 60 minutes, where it then increments by 60 minutes. Once it reaches 120 minutes, I want the stepper to increment by 1440 minutes which is 1 day. My code is having issues, however, and another problem I'm running into is decrementing the stepper so that you can go from a day back down to an hour and then back down to minutes.
This is my code right now:
func stepperValueChanged(stepper: UIStepper) {
var value = Int(stepper.value)
stepper.minimumValue = 0
if (stepper.value != 0 && stepper.value <= 60) {
stepper.stepValue = 5
stepper.value -= 5
stepper.value += 5
value = Int(stepper.value)
reminderSubLabel.text = "\(value) minutes before"
if stepper.value == 60 {
reminderSubLabel.text = "\(value / 60) hour before"
}
} else if stepper.value >= 60 && stepper.value <= 180 {
if stepper.value == 60 {
reminderSubLabel.text = "\(value / 60) hour before"
stepper.value -= 5
stepper.value += 60
} else {
//stepper.value = 120
value = Int(stepper.value / 60)
stepper.stepValue = 60
stepper.value -= 60
stepper.value += 60
reminderSubLabel.text = "\(value) hours before"
}
if stepper.value == 120 {
stepper.value = 1440
stepper.value -= 1320
stepper.value += 1440
//stepper.stepValue = 1320
}
}
if stepper.value == 0 {
stepper.stepValue = 5
stepper.value = 0
reminderSubLabel.text = "At the time of the event"
}
stepper.minimumValue = 0
stepper.maximumValue = 20160
print("\(stepper.value)")
}
You should not bind to the value property of UIStepper. Try use sort of abstraction where value of stepper is x and value you need in minutes is y. All you need is to implement y = f(x) in stepperValueChanged(stepper:). It's pretty simple if step is 1.
var minutes = 0
#IBAction func stepperValueChanged(_ stepper: UIStepper) {
let value = Int(stepper.value)
var minutes = 0
for step in 0..<value {
if step <= 11 {
minutes += 5
} else if step <= 12 {
minutes += 60
} else {
minutes += 1440
}
}
self.minutes = minutes
}
I am working on a stopwatch app that begins flashing when approaching limit and again when overtime. Everything works great except for when the app is pushed to the background and then brought to the foreground again. At that time the animation will not start.
I have tried moving the code to applicationWillEnterForeground and applicationDidBecomeActive but it produces a fatal error:
unexpectedly found nil while unwrapping an Optional value
Am I missing something basic here?
There is more to the code of course (calculations, etc.), but this is relevant section. (Edit: I've expanded this to provide a better view of what I'm doing.)
#IBAction func start(_ sender: AnyObject) {
if timerOn == 0 {
myTimer = Timer.scheduledTimer(timeInterval: 0.1, target:self, selector: #selector(ViewController.updateCounter), userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate
timerOn = 1
redOrYellow = "white"
startButton.superview?.sendSubview(toBack: startButton)
background.superview?.sendSubview(toBack: background)
pickerMask.isHidden = false
}
}
....
func animateLight() {
if redOrYellow == "red" {
print("red")
//background.superview?.sendSubview(toBack: background)
UIView.animate(withDuration: 0.3, delay: 0.01, options:[UIViewAnimationOptions.repeat, UIViewAnimationOptions.allowUserInteraction], animations: {
self.background.backgroundColor = UIColor.red
self.background.backgroundColor = UIColor.white
}, completion: nil)
} else if redOrYellow == "yellow" {
print("yellow")
//background.superview?.sendSubview(toBack: background)
UIView.animate(withDuration: 1, delay: 0.1, options:[UIViewAnimationOptions.repeat, UIViewAnimationOptions.allowUserInteraction], animations: {
self.background.backgroundColor = UIColor.yellow
self.background.backgroundColor = UIColor.white
}, completion: nil)
}
}
func updateCounter() {
let currentTime = NSDate.timeIntervalSinceReferenceDate
var elapsedTime: TimeInterval = currentTime - startTime
totalTime = elapsedTime
if totalTime >= partBaseTimes[numberOfSelectedPart] {
if redOrYellow != "red" {
redOrYellow = "red"
animateLight()
}
} else if totalTime >= (partBaseTimes[numberOfSelectedPart] - 30) {
if redOrYellow != "yellow" {
redOrYellow = "yellow"
animateLight()
}
}
let minutes = UInt8(elapsedTime / 60.0)
elapsedTime -= (TimeInterval(minutes) * 60)
let seconds = UInt8(elapsedTime)
elapsedTime -= TimeInterval(seconds)
let fractionOfSecond = UInt8(elapsedTime * 10)
let strMinutes = String(format: "%02d", minutes)
let strSeconds = String(format: "%02d", seconds)
let strFractionOfSecond = String(format: "%01d", fractionOfSecond)
talkTimer.text = "\(strMinutes):\(strSeconds).\(strFractionOfSecond)"
}