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.
Related
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()
}
}
I just implemented about 30 textfield inputs and after running my app I realized that I am not able to escape the numpad in anyway?? Has anyone figured out a simple solution to escape/remove the numbpad/keyboard?? I found the solution of adding a toolbar to the keyboard but that would require me to do that 30 times which doesn't sound efficient to me.
Use this
extension UITextField {
#IBInspectable var doneAccessory: Bool {
get {
return self.doneAccessory
}
set (hasDone) {
if hasDone {
addDoneButtonOnKeyboard()
}
}
}
func addDoneButtonOnKeyboard() {
let doneToolbar: UIToolbar = UIToolbar(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
doneToolbar.barStyle = .default
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction))
let items = [flexSpace, done]
doneToolbar.items = items
doneToolbar.sizeToFit()
self.inputAccessoryView = doneToolbar
}
#objc func doneButtonAction() {
self.resignFirstResponder()
}
}
and add this single line to your text fields
textField.doneAccessory = true
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()
}
}
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)
this is a test code,I want to add uiview to storyBoard ,and wait 1 second, and remove it .but the uiview doesn't appear , the code is down
var uiview1 = UIView()
uiview1.frame = CGRectMake(0, 0, 50, 50)
uiview1.backgroundColor = UIColor.blackColor()
self.view.addSubview(uiview1)
sleep(1)
uiview1.removeFromSuperview()
sleep() is not a good idea in this way. It will become totally unresponsive and block. Use NSTimer.scheduledTimerWithTimeInterval instead.
class ViewController: UIViewController {
var uiview1 = UIView()
override func viewDidLoad() {
super.viewDidLoad()
uiview1.frame = CGRectMake(0, 0, 50, 50)
uiview1.backgroundColor = UIColor.blackColor()
self.view.addSubview(uiview1)
var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("dismissView"), userInfo: nil, repeats: false)
}
func dismissView() {
uiview1.removeFromSuperview()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}