I was trying to build a simple ios timer app using swift3. I successfully created an app using the following code. It has three buttons, one to start the timer, one to stop the timer which means reset and one to pause the timer. all the buttons are working but when I click on start once again while the timer is running, the timer interval speeds up (means calls the selector function twice within a second). how to solve this problem.
Here is my code
#IBOutlet weak var lbl: UILabel!
var time = 0
var timer = Timer()
#IBOutlet weak var start: UIButton!
#IBOutlet weak var stop: UIButton!
#IBOutlet weak var pause: UIButton!
#IBAction func start(_ sender: AnyObject) {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.action), userInfo: nil, repeats: true)
}
#IBAction func stop(_ sender: AnyObject) {
//timer.invalidate()
time = 0
lbl.text = "0"
}
#IBAction func pause(_ sender: AnyObject) {
timer.invalidate()
}
override func viewDidLoad() {
super.viewDidLoad()
}
func action() {
time += 1
lbl.text = String(time)
}
If your start method runs while a timer is already active, you create a second timer. Now you have two timers calling the same action, which accounts for your problem. Scheduled timers are retained by their run loops, so even though you don't have a reference to the old timer, it's still there.
At a minimum you need to either invalidate the old timer or else just keep using the one you have. But there are some things that would help make the code better:
Your timer attribute should probably be a Swift optional. Initializing it as Timer() doesn't do anything useful. It would make more sense if timer could be nil when no timer should be running.
You should probably disable your "start" button when a timer is running. It doesn't make sense for it to be active when the timer has already started. You could do this by setting up an IBOutlet for the button and changing the value if the button's isEnabled property.
One Mistake in Source code.
First Stop exist timer then start new timer.
Code for that is below.
#IBAction func start(_ sender: AnyObject) {
timer.invalidate()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.action), userInfo: nil, repeats: true)
}
And for stop timer use invalidate timer function.
#IBAction func stop(_ sender: AnyObject) {
timer.invalidate()
}
I think what you need to do is invalidate if there is a timer running
#IBAction func start(_ sender: AnyObject) {
if (timer) {
timer.invalidate()
}
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.action), userInfo: nil, repeats: true)
}
#IBOutlet weak var lbl: UILabel!
var time = 0
var timer : Timer?
var isPause = false
#IBOutlet weak var start: UIButton!
#IBOutlet weak var stop: UIButton!
#IBOutlet weak var pause: UIButton!
#IBAction func start(_ sender: AnyObject) {
timer?.invalidate()
timer = nil
isPause = false
time = 0
lbl.text = "0"
// or call stop(self.stop)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.action), userInfo: nil, repeats: true)
}
#IBAction func stop(_ sender: AnyObject) {
timer?.invalidate()
timer = nil
isPause = false
time = 0
lbl.text = "0"
}
#IBAction func pause(_ sender: AnyObject) {
timer.invalidate()
isPause = !isPause // second click to resum...
}
override func viewDidLoad() {
super.viewDidLoad()
}
func action() {
if !isPause {
time += 1
lbl.text = String(time)
}
}
Related
So I have a slider and a startTimer button that calls a nextFood() function via a Timer.scheduledTimer interval based on the slider's value.
What I'm trying to do is even after I pressed the startTimer button, if I move the slider to a different value, the scheduledTimer should adjust to the new value and call the nextFood function according to the new interval without trying to press the startTimer button again.
My code:
#IBOutlet var sliderVal: UISlider!
#IBAction func slider(_ sender: UISlider) {
delayLabel.text = "Delay: " + String(Int(sender.value)) + "s"
}
//start timer according to slider val
var timer = Timer()
#IBAction func startTimer(_ sender: UIButton) {
//print(Int(sliderVal.value))
startButton.isEnabled = false
timer = Timer.scheduledTimer(timeInterval: Double(Int(sliderVal.value)), target: self, selector: #selector(ViewController.nextFood), userInfo: nil, repeats: true)
}
So far my code only works for the value the slider is set when the startTimer button is pressed initially but doesn't adjust when I move the slider. Any help is much appreciated!
Short Answer: You can't change the time interval of the timer once it has started.
What you can do is this: Upon slider action, invalidate the previous timer, create a new timer, and fire it with the new interval.
This is also related to your issue.
You don't need to create two #IBActions. You can create one common #IBAction and connect the button and slider action to that.
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var startButton: UIButton!
#IBOutlet weak var slider: UISlider!
var timer: Timer?
#IBAction func updateTimer(_ sender: Any) {
label.text = "Delay: " + String(Int(slider.value)) + "s"
startButton.isEnabled = false
timer?.invalidate()
timer = nil
timer = Timer.scheduledTimer(timeInterval: Double(Int(slider.value)), target: self, selector: #selector(ViewController.nextFood), userInfo: nil, repeats: true)
}
#objc func nextFood() {
///
}
}
Note: Before initialising new Timer instance, invalidate the previous instance. Else multiple timer instances will be calling the nextFood method
You can do that with a DispatchSourceTimer which can be restarted without recreating the timer instance
var timer : DispatchSourceTimer?
#IBOutlet var sliderVal: UISlider!
#IBAction func slider(_ sender: UISlider) {
let delay = Int(sender.value)
delayLabel.text = "Delay: \(delay) s"
startTimer(with: delay)
}
//start timer according to slider val
#IBAction func startTimer(_ sender: UIButton) {
//print(Int(sliderVal.value))
startButton.isEnabled = false
startTimer(with: Int(sliderVal!.value))
}
func startTimer(with delay: Int) {
let interval : DispatchTime = .now() + .seconds(delay)
if timer == nil {
timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer!.schedule(deadline:interval, repeating: TimeInterval(delay))
timer!.setEventHandler {
DispatchQueue.main.async {
self.nextFood()
}
}
timer!.resume()
} else {
timer!.schedule(deadline:interval, repeating: TimeInterval(delay))
}
}
Every time I hit the button I would like the button to be hidden for 3 seconds then after the 3 seconds are up I would like the button to not be hidden.
#IBOutlet var save: UIButton!
#IBAction func button(_ sender: Any) {
}
You can just schedule a closure to be executed on the main thread with a 3second delay that unhides your button.
#IBOutlet var save: UIButton!
#IBAction func button(_ sender: Any) {
save.isHidden = true
DispatchQueue.main.asyncAfter(deadline: .now()+3, execute: {
save.isHidden = false
})
}
You can use CGD:
#IBOutlet var save: UIButton!
#IBAction func button(_ sender: Any) {
self.button.alpha = 0.0
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.button.alpha = 1.0
}
}
or you can use perform(_:with:afterDelay:)
#IBOutlet var save: UIButton!
#IBAction func button(_ sender: Any) {
self.button.alpha = 0.0
perform(#selector(showButton), with: nil, afterDelay: 3)
}
#objc func showButton() {
self.button.alpha = 1.0
}
Actually if you use Google, you will find a lot of examples. Use something like this:
var timer: Timer!
#IBOutlet var save: UIButton!
#IBAction func button(_ sender: Any) {
save.isHidden = true
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(runTimedCode), userInfo: nil, repeats: false)
}
func runTimedCode() {
save.isHidden = false
}
This is my first post so I hope this is a valid question. I've searched the forums for an answer to this with no luck. Below is my code for a stopwatch app. Problem I am having is when the play button is clicked multiple times it is ticking multiple seconds at a time. How do I safely stop this from happening?
ViewController
import UIKit
class ViewController: UIViewController {
// MARK: Properties
#IBOutlet weak var timerLabel: UILabel!
var timer = NSTimer()
var time = 0
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: Functions
func increaseTimer() {
time++
let formattedTime = String(format:"%02d:%02d", (time/60), time%60)
timerLabel.text = "\(formattedTime)"
}
// MARK: Actions
#IBAction func btnPlay(sender: AnyObject) {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self,
selector: Selector("increaseTimer"), userInfo: nil, repeats: true)
}
#IBAction func btnStop(sender: AnyObject) {
timer.invalidate()
}
#IBAction func btnReset(sender: AnyObject) {
timer.invalidate()
time = 0
timerLabel.text = "00:00"
}
}
EDIT: SOVED http://imgur.com/mqw1Xnp
Make your button into a start/stop button. Use a boolean instance variable to keep track of whether the timer is running or not. If it is, stop it. If it's not, start it.
Alternately, make the code that starts the timer set button.disabled = true
This is my code.
//
// ViewController.swift
// Stopwatch
//
// Created by Orkun Atasoy on 12.09.15.
// Copyright (c) 2015 Orkun Atasoy. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var timerButton: UIButton!
var timer : NSTimer?
var ms = 0
#IBAction func buttonTapped(sender: AnyObject) {
timerButton.setTitle("Stopp", forState:UIControlState.Normal)
self.timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
func update() {
self.ms++
timerLabel.text = String(self.ms)enter code here
}
}
The Problem is when i run the build it comes the introscreen with big text "Stopwatch" and the it is like freezed there. But it should come a label with button downside which has a text "start". When i click the button it should start counting and the label should change to "stopp". When i click again the it should stop the timer.
I dont get what the Problem ist. I am a Swift newbie. I would be pleased if you could help me.
Thank you for your attention
EDIT >> the label text is at the beginning "00:00".
Looks like you are messing with IBOutlet connections and I have corrected some of your code and here is working code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var timerButton: UIButton!
var timer : NSTimer?
var ms = 0
override func viewDidLoad() {
timerLabel.text = "00:00"
}
#IBAction func buttonTapped(sender: AnyObject) {
if timerButton.currentTitle == "Stopp" {
timer?.invalidate()
timerButton.setTitle("Start", forState:UIControlState.Normal)
} else {
timerButton.setTitle("Stopp", forState:UIControlState.Normal)
self.timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
}
func update() {
self.ms++
timerLabel.text = String(self.ms)
}
}
And First of all remove all outlets from your storyboard view controller and connect it this way:
And if you want check THIS sample project.
The method you call when the timer fires is incorrect. From the documentation:
The selector should have the following signature: timerFireMethod: (including a colon to indicate that the method takes an argument). The timer passes itself as the argument, thus the method would adopt the following pattern:
- (void)timerFireMethod:(NSTimer *)timer
So your method should be:
func update(timer: NSTimer) {
self.ms++
timerLabel.text = String(self.ms)
}
And you should set up your timer as:
NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "update:", userInfo: nil, repeats: true)
There are a few more things I could mention - such as no need to use self when unambigously using variables, and using a dispatch timer instead of an NSTimer, but this should at least solve your immediate problem.
How do i stop my timer from running? Not like a pause, but a stop.
import UIKit
class LastManStandingViewController: UIViewController {
#IBOutlet weak var timeLabel: UILabel!
#IBOutlet weak var timeTextbox: UITextField!
#IBOutlet weak var startButton: UIButton!
#IBOutlet weak var stopButton: UIButton!
var myCounter = 0
var myTimer : NSTimer = NSTimer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
timeLabel.text = String(myCounter)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func startTimer(){
myTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateTimer"), userInfo: nil, repeats: true)
println("func startTimer")
}
func stopTimer(){
myTimer.invalidate()
myCounter = 0
timeLabel.text = String(myCounter)
println("func stopTimer")
}
func updateTimer(){
timeLabel.text = String(myCounter++)
println("func updateTimer")
}
#IBAction func startButton(sender: AnyObject) {
startTimer()
}
#IBAction func stopButton(sender: AnyObject) {
stopTimer()
}
}
I can start the timer, but when i press the Stop button, it reset itself, and starts counting again. It doesn't stop.
Made it work. Something was buggy with my project! Fixed it by removing the button and re-adding them. Looks like i had a duplicate or something.
You don't have to use Selector:
#IBAction func startButton(sender: AnyObject) {
myTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateTimer:", userInfo: nil, repeats: true)
}
Also, the timer passes itself to the selected method, so you can invalidate it inside the method if you need:
func updateTimer(timer: NSTimer) {
timeLabel.text = String(Counter++)
timer.invalidate()
}
Or if the timer is an instance variable:
myTimer.invalidate()
myTimer = nil
It's a good thing to nil the instance variable timer after having invalidated it, it avoids further confusion if you need to create another timer with the same variable. Also, method names and variables should begin with a lowercase letter.
Screenshot to show the timer invalidated and set to nil.
Update for Swift 2.2+
See https://stackoverflow.com/a/36160191/2227743 for the new #selector syntax replacing Selector().
you can use this when some condition is met and you want to stop timer:
Timer.invalidate()
Here is simple example :
func UpdateTimer(){
timeLabel.text = String(Counter++)
if timeLabel.text == String("5") {
Timer.invalidate()
}
}
this will stop timer.
You can modify it as per your need.
As pre Swift 2.2
let printTimer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: #selector(printDescription), userInfo: nil, repeats: true)
func printDescription() {
print("Print timer will print every after 2.0 seconds")
}
Print timer will print every after 2.0 seconds