How to set several subsequent countdown timers in Swift - 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.

Related

Why is my timer function having irregular intervals?

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.

Nil cannot be assigned to type 'Timer'?

Hi there I am completing some research for Alzheimers Disease - I want to be able to record the time it takes to complete a drawing (it should only take a few seconds for the patient to draw). I want to record both the time spent with apple pencil on tablet and the time spent overall to complete a drawing (time on tablet plus time in between strokes).
I have created this application so far but can't get the timer to work.
I have the drawing/scribble board down pat.
I have tried many different approaches but I am just not experienced enough in code to work out why it is not starting the timer when the apple pencil hits the tablet. The code below is for the ViewController script.
Only one of the timers for = time spent drawing, has been created so far. But I can't even get that to work.
Tried changing the script, tried asking friends. Im quite new to swift so any help is much appreciated.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var canvasView: CanvasView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func clearCanvas(_ sender: Any) {
canvasView.clearCanvas()
timer.invalidate()
seconds = 0
timeDrawing.text = "\(seconds)"
}
#IBOutlet weak var timeDrawing: UILabel!
var seconds = 0
var timer = Timer()
var isTimerRunning = false //This will be used to make sure only one timer is created at a time.
var resumeTapped = false
var touchPoint:CGPoint!
var touches:UITouch!
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
}
private(set) var from: Date?
#objc func updateTimer() {
let to = Date()
let timeIntervalFrom = (from ?? to).timeIntervalSince1970
let time = to.timeIntervalSince1970 - timeIntervalFrom
timeDrawing.text = "\(round(time))" //This will update the label.
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
guard let touch = touches.first else { return }
let touches = touch.location(in: canvasView) // or maybe ...(in: self)
if touch.type == .pencil {
if !isTimerRunning {
from = Date()
runTimer()
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
if !isTimerRunning {
timer.invalidate()
timer = nil
from = nil
timeDrawing.text = "0"
}
}
}
I was hoping when the apple pencil touched the tablet it would start the timer. And then when the pencil left the tablet it would stop one timer and start one of the timers (yet to be implemented). (i have yet to add another timer for the time in between strokes, any help with that would be appreciated too.)
Only optionals can take nil values, you can make the timer object as optional like this
var timer:Timer?
Yes it makes sense. Edited

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.

How do I add a timer using Swift?

I have come across a lot of questions similar to this, but many were for older versions of Xcode, or simply did not work.
I'm using Xcode Version 8.3.2 (8E2002) and Swift coding language. I don't know much about coding, but am young and eager to learn!
I'm creating a clicker game that will give you money per second that you are on the game itself. So if you idle for 2 minutes, it would give you $120 ($1per second #120 sec). In addition to this, you also can earn money from clicking the main object.
Here is my coding so far:
import UIKit
class ViewController: UIViewController {
var score = 0
var add = 1
func addpersec() {
score += 1
}
//func used to add to the score based timer. Aka, adding 1 per second
#IBOutlet weak var scorecount: UILabel!
#IBAction func clicks(_ sender: Any) {
score += 1
scorecount.text = "Honey: \(score)"
}
#IBOutlet weak var Bees: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
class ViewController: UIViewController {
var timer: Timer? = nil // Property
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(handleTimer), userInfo: nil, repeats: true)
}
func handleTimer(_ timer: Timer) {
print("Timer ticking!")
}
}
To invalidate the timer, call self.timer?.invalidate()
Your question seems to be related to iOS UI, so I don't know if my answer makes sense.
For general purpose delayed function execution (like Javascript's setTimeout), you can use a DispatchQueue
// have this as a global somewhere
let bg = DispatchQueue(label: "bg", qos: .background, target: nil)
// anywhere else in your code:
// First decide the time to execute your function
let delayInSeconds = 3
let when = DispatchTime.now() + delayInSeconds
// call it
bg.asyncAfter(deadline: when) {
// code to execute later (this is the body of an anonymous function)
}

Looping Label Change Background task in Swift

I was looking for a way in Swift to loop a changing label that cycles through an array of Strings. Most ways I've tried have stopped all other tasks while the loop was running.
You're view controller could look something like this:
class ViewController: UIViewController {
#IBOutlet weak var cycleLabel: UILabel!
var strings: [String]!
var timer: NSTimer!
var index: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
self.strings = ["Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "Vestibulum", "erat", "lacus", "congue"]
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.cycleLabel.text = self.strings[self.index]
}
#IBAction func beginCyclingTapped(sender: UIButton) {
let interval = 1.0
if self.timer.valid {
self.timer.invalidate()
}
self.timer = NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "updateLabel", userInfo: nil, repeats: true)
}
func updateLabel() {
self.index += 1
self.cycleLabel.text = self.strings[self.index % self.strings.count]
}
}
This code will update the label text to the next string in the strings property every one second. If you'd like a different interval, change the interval constant in the beginCyclingTapped(:) method. The label will start restart from the beginning of the strings array after it reaches the last element in that array. The if statement in beginCyclingTapped(:) ensures that multiple timers are not scheduled to update that label, which would result in the label getting updated more frequently than desired. Also, make sure you hook up the IBOutlet to a UILabel on your Storyboard.