I guys I have a working countdown timer which I can set manually and countdown from this value.
What I'm trying to achieve now though is set my INTERVALS counter to say 3 and loop through the countdown timer the amount of times that I set in this counter then when the last interval is complete the timer finishes
sounds straight forward in my head I know I need a loop somewhere but everything I have tried just hast worked this is my current code before adding a loop. Have tried adding a while statement instead of the if statement but that didn't do the job
I've now added this loop but still no joy. Anyone know how to do this?
func runIntervalTimer() {
timerForIntervals = NSTimer.scheduledTimerWithTimeInterval(1, target: self,
selector: Selector("updateTimeInterval"), userInfo: nil, repeats: true)
}
func updateTimeInterval() {
do {
if intervalCountdown > 0 {
intervalHasBeenSet = true
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60) }
} while intervalHasBeenSet == true
intervalHasBeenSet = false
intervalCountdown = 0
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
#IBAction func InterValTimerIncrease(sender: AnyObject) {
intervalCountdown++
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
#IBAction func IntervalTimerDecrease(sender: AnyObject) {
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
#IBAction func IntervalStart(sender: AnyObject) {
runIntervalTimer()
}
You're probably in an infinite loop here:
do {
if intervalCountdown > 0 {
intervalHasBeenSet = true
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60) }
} while intervalHasBeenSet == true
Your condition is intervalHasBeenSet == true but it's never being set to false inside the loop, so it can never exit.
If you want the timer to count down each interval, I think all you need to do is:
func updateTimeInterval() {
println(intervalCountdown)
if intervalCountdown > 0 {
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
}
Because the updateTimeInterval should be called each interval by NSTimer, you shouldn't need a loop inside this function.
To have the timer count down multiple times, you need an extra variable (intervalNum):
func updateTimeInterval() {
println(intervalCountdown)
if intervalCountdown > 0 && intervalNum > 0 {
// countdown in the current interval
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
else
{
// move on to the next interval
intervalNum--
intervalCountdown = 20 // or grab the original value from somewhere
}
if (intervalNum == 0)
{
// stop the timer
timerForIntervals.invalidate()
}
}
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
}
Is there a way to run the code simultaneously on different devices? Let's say I want that when I click on a button on one of the devices, the function starts simultaneously on both the first and the second device? I tried to use a timer with a time check for 3 seconds ahead, but the function is triggered with a delay of 0.5 seconds on the second device
func getEventTime() -> UInt64{
let now = Date()
let interval = now.timeIntervalSince1970
let result = (UInt64(interval) + (3)) * 1000
return result
}
func getCurrentTime() -> UInt64{
let now = Date()
let interval = now.timeIntervalSince1970
let result = UInt64(interval * 1000)
return result
}
func startTimer(time : UInt64){
Timer.scheduledTimer(withTimeInterval: 0.0001, repeats: true) { timer in
switch getCurrentTime() {
case time - 1000 :
DispatchQueue.main.async {
countdownImageTimer.image = UIImage(named: "Start")
}
break
case time :
DispatchQueue.main.async {
countdownImageTimer.removeFromSuperview()
}
self.setShips()
timer.invalidate()
break
default:
break
}
}
}
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 got some Swift code to print every character of a specific word ("stackoverflow") and also with a specific delay (1.0s).
To understand my thoughts look at the pseudo code:
print("s")
wait(1s)
print("t")
wait(1s)
print("a")
wait(1s)
print("c")
wait(1s)
print("k")
wait(1s)
...
Ok - below you can find my code written in Swift:
var mystring="stackoverflow"
var counter=0.0
for i in mystring {
Timer.scheduledTimer(
timeInterval: Double(counter),
target: self,
selector: #selector(self.myfunc(_:)),
userInfo: String(i),
repeats: false)
counter=counter+1.0
})
}
func myfunc(_ timer: Timer) {
let value: String? = timer.userInfo! as? String
print ("Value: \(value as String?)")
}
But how is it possible to kill all myfunc-calls after the for loop has finished? How to kill the different Timers that I didn't declared with a variable to avoid the override of the last Timer??
Why not one timer and a few lines additional logic. The code prints the first character of the string when the timer fires and then drops the first character until the string is empty. At the end the timer gets invalidated.
let string = "stackoverflow"
var temp = Substring(string)
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
print(temp.prefix(1))
temp = temp.dropFirst()
if temp.isEmpty { timer.invalidate() }
}
or as ticker
let string = "stackoverflow"
var counter = 1
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
print(string.prefix(counter))
if counter == string.count { timer.invalidate() }
counter += 1
}
Edit : For those who argue that this is not the best answer it's the best for his current programming technique and to suggest a good one here is a solution in edit part for his main issue with no timers at all
Edit://////
var counter = 0.0
for i in mystring {
DispatchQueue.main.asyncAfter(deadline: .now() + counter) {
print("\(String(i))")
}
counter = counter + 1
}
/////////
declare var
var timerarr = [Timer] = []
then add every created timer to the array
for i in mystring {
let t = Timer.scheduledTimer(
timeInterval: Double(counter),
target: self,
selector: #selector(self.myfunc(_:)),
userInfo: String(i),
repeats: false)
counter=counter+1.0
})
timerarr.append(t)
}
and loop to stop
for i in 0..<timerarr.count {
let tim = timerarr[i]
tim.invalidate()
}
timearr = []
I'm using this function to make the text write letter by letter:
extension SKLabelNode {
func setTextWithTypeAnimation(typedText: String, characterInterval: NSTimeInterval = 0.05) {
text = ""
self.fontName = "PressStart2P"
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
for character in typedText.characters {
dispatch_async(dispatch_get_main_queue()) {
self.text = self.text! + String(character)
}
NSThread.sleepForTimeInterval(characterInterval)
}
}
}
And, if the user clicks the screen, I want to make the for loop stop and show the complete text instantly.
I would do something like this:
var ignoreSleeper = false
#IBAction func pressButton(sender: UIButton) {
ignoreSleeper = true
}
extension SKLabelNode {
func setTextWithTypeAnimation(typedText: String, characterInterval: NSTimeInterval = 0.05) {
text = ""
self.fontName = "PressStart2P"
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
for character in typedText.characters {
dispatch_async(dispatch_get_main_queue()) {
self.text = self.text! + String(character)
}
if(!ignoreSleeper){
NSThread.sleepForTimeInterval(characterInterval)
}
}
}
}
Edit: like #Breek already mentioned
I'd suggest to implement a tiny NSTimer with a counter for the number of chars to display. Start the Timer with a repeat count of typedText.characters.count and the desired delay and you're good to go (with one thread). Increment the number of chars counter on each timer loop. You can stop this timer at any time with a button press by calling invalidate on the timer.
Example
var timer: NSTimer?
var numberOfCharsToPrint = 1
let text = "Hello, this is a test."
func updateLabel() {
if numberOfCharsToPrint == text.characters.count {
welcomeLabel.text = text
timer?.invalidate()
}
let index = text.startIndex.advancedBy(numberOfCharsToPrint)
welcomeLabel.text = text.substringToIndex(index)
numberOfCharsToPrint++;
}
Then initialize your timer whenever you want the animation to start.
timer = NSTimer.scheduledTimerWithTimeInterval(0.25, target: self, selector: "updateLabel", userInfo: nil, repeats: true)
You can invalidate/stop the timer at any given time with timer?.invalidate().