Why is my timer function having irregular intervals? - swift

I have attached my code for you to see.
What I want to achieve:
Timer repeats 5 times
Hardcoded time of 10s
the display of a number (i) reduces by 1 with each iteration
What happens:
i goes straight to 1
countdown goes something like 5, 0, -2, -4, -8, -13 NOT 10,9,8,7,6,5,4,3,2,1,0 then repeat
var timer = Timer()
var i = 5
// number of repeats is 5
var time = 10
// timer is 10s
class ViewController: UIViewController {
#IBOutlet weak var displayTime: UILabel!
#IBOutlet weak var displayI: UILabel!
#IBAction func startButton(_ sender: Any) {
for _ in 1...5 {
displayI.text = String(i)
countdown()
i -= 1
}
}
func countdown() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(Action), userInfo: nil, repeats: true)
}
#objc func Action() {
time -= 1
displayTime.text = String(time)
if time == 0 {
timer.invalidate()
}
}
Can anyone help?
(I also tried a repeat while loop where it repeats while (i >= 1) so it should stop when i=0)

Remember that loops (any kind) runs very quickly. Your for _ in 1...5 loop completes before the first call to Action even starts. This is because countdown() completes almost immediately, not after the timer it creates has been invalidated.
When you create a timer, it runs asynchronously to the rest of your code. So the line immediately after countdown() is not when the timer will end. The line in the if time == 0 statement is.
Note that even if the timer were synchronous, your code wouldn't have worked because you didn't reset time to 10 when each timer ends.
Anyway, the five timers will decrement time almost at the same time, and depending on when the UI refreshes, you will see that time has decremented a different amount.
You seem to just want to count from 10 to 0 five times. You can do this with just one timer:
#objc func Action() {
time -= 1
displayTime.text = String(time)
if time == 0 {
i -= 1
time = 11
}
if i == 0 {
timer.invalidate()
}
}
#IBAction func startButton(_ sender: Any) {
countdown()
}
i counts how many times this is, and time counts the number to display.

Related

How to create multiple delays using a dispatch queue?

Is it possible to use DispatchQueue to create multiple delays in my code?
For example, with sleep I can do:
for index in 1...20 {
do {
sleep(1)
// Code I want to run every 1 second for 20 times
}
}
completionHandler()
But I want to use DispatchQueue instead:
for index in 1...20 {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// Code I want to run every 1 second for 20 times
}
}
completionHandler()
which doesn't seem to be working (it's not waiting 1 second each time)
Edit:
At the end of running the code for 20 times, I want to call the completionHandler to return a result of the code in the loop. Problem is that this does not wait for the DispatchQueue
// Code I want to run every 1 second for 20 times
What you're looking for is a Timer.
https://developer.apple.com/documentation/foundation/timer
var timer : Timer?
var times = 0
#objc func fired(_ t:Timer) {
times += 1
print("do your code") // do your code here
if times == 20 {
t.invalidate()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self,
selector: #selector(fired), userInfo: nil,
repeats: true)
}
You will see that print("do your code") runs every 1 second 20 times.
You need to add a different delay Double(index)
for index in 1...20 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index)) {
// Code I want to run every 1 second for 20 times
}
}

How to set several subsequent countdown timers in Swift

To begin with, in general, I want to build the functionality of the program on a timer, which will alert you about the specified breaks, etc. The program is for concentration.
When saving all the variables that we set at the very beginning, when you press the button, the timer should start. It must perform a specific cycle (period) that we set earlier.
But I have something wrong with the implementation of exactly the same cycle. It seems that all variables are saved, but why the label does not change them ...
Ideally, you should first run Work -> Short break -> Work -> Short Break -> Work -> Short Break -> Work -> Short Break -> Long Break and then repeat depending on how many Cycles are installed. But for some reason, I have Work -> Short break -> Short break ...
From you I just want to hear the opinion of what my mistake may be and how to solve it?
For my "code" do not pay attention and do not scold, I know myself. Now I just want to learn how to write and understand what I am writing. The code will of course be better over time.
My app looks like this:
import UIKit
class ViewController: UIViewController {
var time: Int = 0
var timer = Timer()
var min: Int = 0
var sec: Int = 0
#IBOutlet weak var shortBreakLabel: UITextField!
#IBOutlet weak var longBreakLabel: UITextField!
#IBOutlet weak var workLabel: UITextField!
#IBOutlet weak var cyclesLabel: UITextField!
#IBOutlet weak var goButton: UIButton!
#IBOutlet weak var minutesLabel: UILabel!
#IBOutlet weak var secondsLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
shortBreakLabel.text = String(5)
longBreakLabel.text = String(15)
workLabel.text = String(25)
cyclesLabel.text = String(16)
saveTimer()
goButton.layer.cornerRadius = 10
}
//GoButton pressed
#IBAction func goButtonAction(_ sender: UIButton) {
timerFunc()
}
func timerFunc() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerDidEndend), userInfo: nil, repeats: true)
}
#objc private func timerDidEndend() {
if (time > 0) {
time -= 1
updateUI()
} else {
timer.invalidate()
}
changeTimeToShortBreak()
changeTimeToWork()
changeTimeToShortBreak()
}
private func updateUI() {
min = (time/60) % 60
sec = time % 60
minutesLabel.text = String(min)
secondsLabel.text = String(sec)
}
func changeTimeToShortBreak() {
if time == 0 {
timer.invalidate()
minutesLabel.text = shortBreakLabel.text
time = Int(minutesLabel.text!)! * 60
timerFunc()
}
}
func changeTimeToWork() {
if time == 0 {
timer.invalidate()
minutesLabel.text = workLabel.text
time = Int(minutesLabel.text!)! * 60
timerFunc()
}
}
func saveTimer() {
minutesLabel.text = workLabel.text
time = Int(minutesLabel.text!)! * 60
}
//Hide keyboard function
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
saveTimer()
self.view.endEditing(true)
}
}
You really have 2 or 3 different things going on here:
Allowing the user to set time intervals
Displaying the time since the last/to the next interval
Keeping track of the time to figure out what state you're currently in or will be in next
I would break this into 2 classes - a state class and a UI class. The state class would just have an array of time intervals and an indicator of which one you're on. Something like this:
var timeIntervals = [ kDefaultWorkInterval, kDefaultShortBreakInterval, kDefaultWorkInterval, kDefaultShortBreakInterval, kDefaultWorkInterval, kDefaultShortBreakInterval, kDefaultWorkInterval, kDefaultShortBreakInterval, kDefaultLongBreakInterval ]
var currentInterval = 0
var timer = Timer()
Rather than setting the timer to fire every second, you simply set it to the time in the timeIntervals [ currentInterval ] element. When it fires, increment currentInterval, get the time interval for that interval, and set the timer to fire in that many seconds.
Next, in your UI class, don't poll for changes to the text fields. Set the UI class (your ViewController) to receive notifications from the text fields when they have been edited. That way you only need to update the state object's time intervals when the user has actually changed them. This is much more efficient than changing them once per second. When that happens call a setter on the state class to update the time intervals.
I would keep the 1 second timer in the ViewController and simply use it for updating the countdown and nothing else.

How to generate a random number between 1 and 75 repeating every 10 seconds in Swift

I have the following code in Swift trying to get a simple random number generator as a simulator for a game.
var randomNumber = 0
override func viewDidLoad() {
super.viewDidLoad()
randomNumber = Int(arc4random_uniform(74) + 1)
label.text = "\(randomNumber)"
}
I'm new to programming Swift but I know to use timer() and import Foundation to use the timer function but I'm not sure how to implement and make it so a new number appears in the label every 10 seconds. Thanks for any help.
Use a Timer with an interval of 10 seconds to pull a new number from an array of numbers. Remove the number from the array so that you don't call the same number twice. When the stop button is pressed, or you are out of numbers call invalidate on the timer to stop it.
class BingoCaller: UIViewController {
#IBOutlet weak var label: UILabel!
var numbers = Array(1...75)
let letters = ["B", "I", "N", "G", "O"]
var timer: Timer?
override func viewDidLoad() {
timer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
let index = Int(arc4random_uniform(UInt32(self.numbers.count)))
let number = self.numbers.remove(at: index)
self.label.text = "\(self.letters[(number - 1) / 15])-\(number)"
if self.numbers.isEmpty {
timer.invalidate()
}
}
}
#IBAction func stop(_ button: UIButton) {
timer?.invalidate()
}
}
Suggestions for next steps:
Add the numbers that have been pulled to a second array. Use that array to populate a tableView so that Gran is able to review the numbers when someone calls "Bingo!".
Use AVSpeechSynthesizer to have the iPhone actually speak the numbers.
Add a reset button to start a new game. Initialize the numbers to Array(1...75), the calledNumbers to [] and start again. It's a good idea to move the Timer loop to its own function so that it can be called from a start button.
You can define a helper array, that would let you check if that number was already returned:
var array = [Int]()
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(ViewController.timerFunction), userInfo: nil, repeats: true)
}
func timerFunction(){
var n = arc4random_uniform(75) + 1
while array.contains(Int(n)){
n = arc4random_uniform(75) + 1
}
array.append(Int(n))
label.text = String(n)
if array.count == 75{
timer?.invalidate()
}
}
This way you are sure that the timer is invalidated when all the numbers have already been used and also avoid index-removal errors.

Game Timer using NSDate with NSTimer? swift

I want to start a timer at a specific date and time, then use that start time as a game timer for the rest of the game. Using "timeIntervalSinceDate" will give me seconds but then trying to get the seconds to display on the gameTimerLabel won't work. I might be coming at this the wrong way. Any advice is welcome.
override func viewWillAppear(animated: Bool) {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
let dateAsString1 = "Fri, 1 April 2016 11:30:00 MST"
let date1 = dateFormatter.dateFromString(dateAsString1)!
var currentTime = NSDate()
var counter = 0
gameTimerLabel.text = String(counter)
gameTimerLabel.text = counter //<- Error:Cannot assign value to type 'Int' to type 'String?'
counter = date1.timeIntervalSinceDate(currentTime) //<- Error:Cannot assign value of type 'NSTimeInterval' (aka 'Double') to type 'Int'
}
A couple of things here
First, when you declare counter, it's inferred to be of type Int
var counter = 0
You can declare it as a double by adding a .0 or specifying it's type:
var counter: NSTimeInternval = 0.0
Next, you can use string interoperability to display the count variable in a string, like this:
gameTimerLabel.text = "\(counter)"
Here's an example view controller using an NSTimer as a counter, it counts in seconds:
class ViewController: UIViewController {
// Setup a timer and a counter for use
var timer = NSTimer()
var counter : NSTimeInterval = 0
#IBOutlet weak var label: UILabel!
// Invalidest the timer, resets the counter and udpates the label
#IBAction func resetTimer(sender: AnyObject) {
// Invalidate the timer, reset the label.
self.timer.invalidate()
self.label.text = ""
self.counter = 0
}
// A button that when pressed starts and stops the timer
// There's no pause/resume, so it's invalidated & created again
// But the counter value reamins the same
#IBAction func timerBttnTouched(sender: AnyObject) {
if self.timer.valid {
self.timer.invalidate()
} else {
self.setupTimer()
}
}
// Does the actual counting everytime the timer calls this method
func timerFired() {
self.counter += 1
self.label.text = "\(self.counter)"
}
// Setups a timer, adds it to the run loop and specifies what method should fire when the timer fires
func setupTimer() {
// Setupt the timer, this will call the timerFired method every second
self.timer = NSTimer(
timeInterval: 1,
target: self,
selector: #selector(self.timerFired),
userInfo: nil,
repeats: true)
// Add the timer to the run loop
NSRunLoop.currentRunLoop().addTimer(
self.timer,
forMode: NSDefaultRunLoopMode)
}
}
An important thing to note when using timers is they may not always be called exactly when you need them to, this should be taken into account according to your desired precision when using a timer.
As discussed in comments, here's the solution using a timer to fire a method that compares two dates and uses a NSDateComponentsFormatter to generate a string for display. The initial date is generated in viewDidLoad but can be created anywhere:
class ViewController: UIViewController {
// Setup a timer and a counter for use
var timer = NSTimer()
var counter : NSTimeInterval = 0
var startDate: NSDate?
override func viewDidLoad() {
// Set the initial date
self.startDate = NSDate()
}
#IBOutlet weak var label: UILabel!
// Invalidest the timer, resets the counter and udpates the label
#IBAction func resetTimer(sender: AnyObject) {
// Invalidate the timer, reset the label.
self.timer.invalidate()
self.label.text = ""
self.counter = 0
}
// A button that when pressed starts and stops the timer
// There's no pause/resume, so it's invalidated & created again
// But the counter value reamins the same
#IBAction func timerBttnTouched(sender: AnyObject) {
if self.timer.valid {
self.timer.invalidate()
} else {
self.setupTimer()
}
}
// Does the actual counting everytime the timer calls this method
func timerFired() {
let now = NSDate()
let difference = now.timeIntervalSinceDate(self.startDate!)
// Format the difference for display
// For example, minutes & seconds
let dateComponentsFormatter = NSDateComponentsFormatter()
dateComponentsFormatter.stringFromTimeInterval(difference)
self.label.text = dateComponentsFormatter.stringFromTimeInterval(difference)
}
// Setups a timer, adds it to the run loop and specifies what method should fire when the timer fires
func setupTimer() {
// Setupt the timer, this will call the timerFired method every second
self.timer = NSTimer(
timeInterval: 1,
target: self,
selector: #selector(self.timerFired),
userInfo: nil,
repeats: true)
// Add the timer to the run loop
NSRunLoop.currentRunLoop().addTimer(
self.timer,
forMode: NSDefaultRunLoopMode)
}
}

Swift: Issue with my NSTimer?

So in general terms, this is what i want my program to do: music will play, randomly generate a number between 3 and 12, then set a timer according to that number, when the timer is finished stop the music for 1 second, then after that one second, play the music again(and repeat the process, generate random number...)
This is the code I have written to do so, it works great the first run, but the second run it starts calling the functions at a faster rate(not following the timer) and sometimes in a different order(you can test this by looking at the print out)
var randomTimeInteger = Int()
var stopMusicTimer = NSTimer()
var startMusicTimer = NSTimer()
override func viewDidLoad(){
playMusic()
}
//Call to get random interger
func generateRandomInteger(){
print("Inside generate random integer")
let lower = 3
let upper = 12
randomTimeInteger = lower + Int(arc4random_uniform(UInt32(upper - lower + 1)))
print("Random Integer is : \(randomTimeInteger)")
initiateTimer()
}
//Start the timer
func initiateTimer() {
print("Inside initate Timer")
//Set Timer
stopMusicTimer = NSTimer.scheduledTimerWithTimeInterval(Double(randomTimeInteger), target: self, selector: "stopMusic", userInfo: nil, repeats: true)
}
//Stop the music
func stopMusic(){
print("Inside stop music")
GameViewController.backgroundAudio?.stop()
startMusicTimer = NSTimer.scheduledTimerWithTimeInterval(3.0, target: self, selector: "playMusic", userInfo: nil, repeats: true)
}
func playMusic(){
print("Inside play music")
GameViewController.backgroundAudio?.play()
generateRandomInteger()
}
Any idea what the problem is? Or if there is a better way to do so? PLEASE HELP!
You have one timer that tries to stop the music every 3 to 12 seconds. And sometimes you create a timer that tries to start the music every 3 seconds. And sometimes you create more of these timers. So eventually you have lots and lots of timers that try starting and stopping the music at random time.
To stop a repeating timer, call invalidate.
And don't initialise the timers as you do, NSTimer() returns nothing useful. Just declare the variables as NSTimer?
There are really numerous viable approaches.
Starting from the top, I would sketch the design as follows:
func playMusic(completion: (ErrorType?)->()) {
play(randomDuration()) { error in
if error != nil {
completion(error)
return
}
delay(1.0, f: play)
}
}
func randomDuration() -> Double {...}
func play(duration: Double, completion: (ErrorType?)->()) {
let player = Player(...)
player.resume()
delay(duration) {
player.suspend()
completion(nil)
}
}
Function delay(_:f:) is implemented in terms of dispatch_after.
You will probably notice, that playMusic runs indefinitely. This is by your requirements, but in practice you need a way to stop it.