Update Progress Bar every milliseconds - swift

I want my Progress Bar to update every milliseconds. How would I do this? Currently, it updates every second, which is not what I want.
EDIT: Sorry, I forgot to mention that I still want the timer label to update every second (So it will go down by seconds not milliseconds: 10, 9, 8) while updating the progress bar every milliseconds or 25 times every second.
Code:
progressBar.transform = progressBar.transform.scaledBy(x: 1, y: 5)
progressBar.layer.cornerRadius = 5
progressBar.clipsToBounds = true
progressBar.layer.sublayers![1].cornerRadius = 5
progressBar.subviews[1].clipsToBounds = true
func startTimer() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerUpdate), userInfo: nil, repeats: true)
}
#objc func timerUpdate() {
if timeRemaining <= 0 {
progressBar.setProgress(Float(0), animated: false)
bonusTimerLabel.text = "0"
bonusTimerLabel.textColor = UIColor(red: 186/255, green: 16/255, blue: 16/255, alpha: 1)
} else {
progressBar.setProgress(Float(timeRemaining)/Float(10), animated: false)
timeRemaining -= 1
bonusTimerLabel.text = "\(timeRemaining)"
}

Firing a function with timer every millisecond is not advisable, reference: https://stackoverflow.com/a/30983444/8447312
So you can fire timer func every 50 millisecond to be safe and update your progress bar. That shouldn't be too observable though.
Also make sure timeRemaining is a Double, and then just try:
func startTimer() {
timer = Timer.scheduledTimer(timeInterval: 0.050, target: self, selector: #selector(timerUpdate), userInfo: nil, repeats: true)
}
#objc func timerUpdate() {
if timeRemaining <= 0 {
progressBar.setProgress(Float(0), animated: false)
bonusTimerLabel.text = "0"
bonusTimerLabel.textColor = UIColor(red: 186/255, green: 16/255, blue: 16/255, alpha: 1)
} else {
progressBar.setProgress(Float(timeRemaining)/Float(20), animated: false)
timeRemaining -= 0.050
bonusTimerLabel.text = "\(Int(timeRemaining))"
}

1 millisecond is 0.001 second.
So change timerInterval property in timer to 0.001:
timer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(timerUpdate), userInfo: nil, repeats: true)

Related

How to make restart button appear when timer ends in Swift?

I'm trying to make a countdown timer in Swift.
When the timer is finished, a restart button appears and I want to make the timer restart.
This is My code so far.
class TimerViewController: UIViewController {
let duration: Double = 5 // duration in seconds
var timer: Timer?
var progressView: UIProgressView!
var timeElapsed: Double = 0
override func viewDidLoad() {
super.viewDidLoad()
// Create the progress view
progressView = UIProgressView(progressViewStyle: .default)
progressView.frame = CGRect(x: 20, y: 100, width: view.frame.width - 40, height: 10)
progressView.progress = 0
view.addSubview(progressView)
// Create the timer
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
}
#objc func timerFired() {
timeElapsed += 0.01
let progress = Float(timeElapsed / duration)
progressView.progress = progress
if timeElapsed >= duration {
timer?.invalidate()
timerEnded()
}
}
func timerEnded() {
// Timer has ended, do something here
// Show the restart button
print("Ended")
let restartButton = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
restartButton.setTitle("Restart", for: .normal)
restartButton.addTarget(self, action: #selector(restartTimer), for: .touchUpInside)
view.addSubview(restartButton)
}
#objc func restartTimer() {
// Restart the timer
timeElapsed = 0
progressView.progress = 0
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
}
}
The problem is timer Progress bar appears well, but restart button doesn't appear.
You might've already figured it out but in-case you didn't, I believe the button is there it's just that you cannot see it. Set a background colour and you should be able to see the button
Add the below statement after setting the button title in your timerEnded button
restartButton.backgroundColor = .gray
See if this helps.

Defining a #selector method when it can't access a programmatically created UILabel

If you define a UILabel programmatically, it must be done so in viewDidLoad()? But, if you were to define a #selector method that needed to set said label, it would be impossible given that the #objc selector method needs to be outside the scope of the viewDidLoad() method, where it cannot access the label. How do you get around this?
class ViewController: UIViewController {
var count = 4
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 150, height: 150))
label.center = CGPoint(x: 250, y: 430)
label.font = .systemFont(ofSize: 130)
label.textColor = UIColor.black
view.addSubview(label)
Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: <#T##Selector#>, userInfo: <#T##Any?#>, repeats: <#T##Bool#>)
#objc func updateLabel() { // ERROR #objc can only be used with members of classes...
if count > 0 {
count -= 1
label.text = "\(count)"
}
}
}
}
Your mistake is that you have put the selector updateLabel inside of viewDidLoad. You should put it outside viewDidLoad so that it is a member of your class, rather than a local function.
To access the label in updateLabel, simply move the declaration of the label outside of both methods.
You should also add a timer parameter to updateLabel so that you can stop the timer when count reaches 0, rather than keeping the timer running forever.
var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
label = UILabel(frame: CGRect(x: 0, y: 0, width: 150, height: 150))
label.center = CGPoint(x: 250, y: 430)
label.font = .systemFont(ofSize: 130)
label.textColor = UIColor.black
view.addSubview(label)
Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateLabel), userInfo: nil, repeats: true)
}
#objc func updateLabel(_ timer: Timer) {
if count > 0 {
count -= 1
label.text = "\(count)"
} else {
timer.invalidate()
}
}

use timer to countdown from what is placed in the uitextfield

My swift code below should take whatever number is placed in the textfield and then count it down in by a single second. I dont know how to take what is in the textfield and get it to be counted down from. Dont worry about making sure the user places a number and not a string in the textfield.
import UIKit
class ViewController: UIViewController {
var enterTime = UITextField()
var lblTime = UILabel()
var startBTN = UIButton()
var timer = Timer()
var counter = 33
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
[enterTime,lblTime,startBTN].forEach{
$0.backgroundColor = .systemRed
view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
enterTime.frame = CGRect(x: view.center.x-115, y: view.center.y-200, width: 60, height: 50)
lblTime.frame = CGRect(x: view.center.x-115, y: view.center.y, width: 60, height: 50)
startBTN.frame = CGRect(x: view.center.x-115, y: view.center.y+200, width: 60, height: 50)
startBTN.addTarget(self, action: #selector(startHit), for: .touchDown)
}
#objc func startHit() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}
#objc func timerAction() {
//This is where the user would take the time from uitextfield and causes it to count down
enterTime.text = counter
lblTime.text = "\(counter)"
}
}
1 ) Invalidate your timer and initiate counter with a value from enterTime textField and start your timer in startHit function.
2 ) In the timerAction function, reduce the counter value by 1 and set it in the lblTime.
You may need to modify your startHit and timerAction functions as follows.
#objc func startHit() {
timer.invalidate()
if let counterText = enterTime.text, let counterValue = Int(counterText) {
counter = counterValue
lblTime.text = String(counter)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}
}
#objc func timerAction() {
counter -= 1
lblTime.text = String(counter)
if ( counter == 0 ) {
timer.invalidate()
}
}

Will someone explain this Swift 4 problem dealing with Timer.scheduledTimer(withTimeInterval: repeats: block:{})

So, I start out by creating my label that is red.
I want to create a timer that changes the color to dark grey every second, and then back to red the next second.
I figured I could create a timer with a time interval of 1 or 2 seconds, have it repeat, and change the labels text color within the block.
I get an error saying:
"Instance member 'aLabelThatIsRed' cannot be used on type 'ViewController'"
It won't let me change the label though. Why?
import UIKit
class ViewController: UIViewController {
#IBOutlet var aLabelThatIsRed: UILabel!
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
let darkGrey = UIColor(displayP3Red: 0.05, green: 0.05, blue: 0.05, alpha: 1.0)
aLabelThatIsRed.textColor = darkGrey
}
You should put that code let timer ... in a instance's method (ex: viewWillAppear)
OR change to this:
var timer: Timer {
return Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {
[weak self] timer in
let darkGrey = UIColor(displayP3Red: 0.05, green: 0.05, blue: 0.05, alpha: 1.0)
self?.aLabelThatIsRed.textColor = darkGrey
}
}
The reason is: aLabelThatIsRed is an instance's variable (property of ViewController), but in your current code, it is used outside of an instance's method. It is the same as you call ViewController.aLabelThatIsRed, which result the same error you have. Doing my solution 1 or 2 will put that variable in an instance's method.
A let variable is initialized before the ViewController is initialized. Hence, self, that is the ViewController, isn't available at the time the timer and its block is created. And hence, you're not able to access aLabelThatIsRed, which is a property on the ViewController.
One fix would be to convert the let to a lazy var. This would work because lazy var is initialized after the ViewController is initialized, hence, self would be available. You might also have to explicitly do a self.aLabelThatIsRed since you're referring to it inside a closure
class ViewController: UIViewController {
#IBOutlet var aLabelThatIsRed: UILabel!
lazy var timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in
let darkGrey = UIColor(displayP3Red: 0.05, green: 0.05, blue: 0.05, alpha: 1.0)
self?.aLabelThatIsRed.textColor = darkGrey
}

How to repeat flip animation?

I'm trying to create my own activity indicator view like twitch chat activity indicator view. But I've tried to use animate .repeat:
override func animate() {
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.repeat, .transitionFlipFromLeft], animations: {
self.flip()
}, completion: nil)
}
and also timer
let timer = Timer.init(timeInterval: 2, target: self, selector: #selector(flip), userInfo: nil, repeats: true)
Here is my flip() function
func flip() {
self.isFlipped = !self.isFlipped
let fromView = self.isFlipped ? self.view1 : self.view2
let toView = self.isFlipped ? self.view2 : self.view1
UIView.transition(from: fromView, to: toView, duration: 1, options: [.transitionFlipFromTop, .showHideTransitionViews], completion: nil)
}
But it did not work. I don't know what I missed. Can you guys help me please?