So I'm doing little timer app in swift and I just have 2 buttons. One to start timer, and one to stop it and reset value to 0. I've figured out everything, and I have this function called timer which increases value for one each second for variable "Time". Problem is that when I click the STOP button, it resets the value to 0 but it keeps counting again.
Question is how do I stop that function from running.
Here is some code
var time = 0
func result() {
time++
print(time)
}
#IBAction func clickToStart(sender: AnyObject) {
result()
}
#IBAction func clickToStop(sender: AnyObject) {
time = 0
print(time)
}
override func viewDidLoad() {
super.viewDidLoad()
var timer = NSTimer()
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("result"), userInfo: nil, repeats: true)
}
Make your timer a member variable and call timer.invalidate() on it
Change your variable timer to be an instance variable. Make it weak, since the system owns it, and when you stop the timer it will be deallocated automatically.
In your clickToStop method, call timer.invalidate().
As others have pointed out using the timer.invalidate() functions, but you can also use flag variables in case you are doing something else not related to time or want another actions to stop.
Basically, you can just create a bool variable and when the user stops the actions, make the bool true. In the other function, make it that if bool is true then don't do the actions unless it's false. This works well for a mute button.
Like in my game, when it's a game over, I have a bool variable touchesInvalid that is at the very top of the touchesBegan function. If the touchesInvalid bool is true, then the user can't do any more actions that involved touch.
Related
I want to implement an action that when I press and hold begins to repeatedly do an action (similar to a scroll button on a Desktop UI). Is there first class support for this in the UIGestureRecognizer/events framework, or do I just roll my own?
E.g.
var timer:Timer?
func killDing() {
self.timer?.invalidate()
self.timer = nil
}
func startDing() {
self.killTimer()
self.timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) {
self.ding() // this is where the repeated code happens
}
}
override func beginTracking(_ touch:UITouch, with event:UIEvent?) -> Bool {
self.startDing()
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
super.endTracking(touch, with: event)
self.killDing()
}
I can of course do this with a LongPressGestureRecognizer as well. My question is whether I need to roll my own ding loop as shown above, or if there's something more first class in UIKit that I'm currently not aware of and should be taking advantage of.
I think you are on the right way. You can use use timers to repeat some actions, but you should add the created timer into a run loop with a common mode, without this mode, the run loop will not call the timer's action while a user is touching the screen
let timer = Timer(timerInterval: interval, repeats: true, block: block)
RunLoop.current.add(timer, forMode: . common)
Also you can use CADisplayLink, to call your action. You can find example of using CADisplayLink in my library, witch can help to you implement animation based on CADisplayLink:
let link = CADisplayLink(target: self, selector: #selector(updateAction(sender:)));
link.add(to: RunLoop.main, forMode: .commonModes);
I am trying to print a value from a slider at regular intervals. But only print the value if it is different to that last printed. I also do not want to miss any of the output values from the slider.
To do this I have created an array and added an element to the start of that array if it is different to the one already at the start. I have then used a repeating NSTimer to regularly call a function that prints the last element in the array before removing it from the array.
What happens when I run the app is the NSTimer stops anything being printed for it's set time, but then all of the elements print at once and more than one of each print. I've tried messing about with lots of different things - this is the closest I have got to making it work.
If you need to know any more info let me know.
I really appreciate any help given, thanks very much.
var sliderArray: [Float] = []
var timer: NSTimer!
let step: Float = 1
#IBAction func sliderValueChanged(sender: AnyObject)
{
let roundedValue = round(slider.value / step) * step
slider.value = roundedValue
if sliderArray.first != slider.value
{
sliderArray.insert(slider.value, atIndex: 0)
}
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(sendSliderPosition), userInfo: nil, repeats: true)
}
func sendSliderPosition()
{
if sliderArray.count > 0
{
print(self.sliderArray.last)
sliderArray.removeLast()
}
}
I would suggest using CADisplayLink. A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display. Which is perfect for your slider case.
This will also not trigger unnecessary call when the slider or the UI is at rest.
class C: UIViewController {
var displayLinkTimer: CADisplayLink?
#IBOutlet weak var slider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
displayLinkTimer = CADisplayLink(target: self, selector: #selector(printSliderValue))
let runLoop = NSRunLoop.mainRunLoop()
displayLinkTimer?.addToRunLoop(runLoop, forMode: runLoop.currentMode ?? NSDefaultRunLoopMode )
displayLinkTimer?.paused = true
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
displayLinkTimer?.paused = true
}
deinit {
displayLinkTimer?.invalidate()
}
func printSliderValue()
{
let step: Float = 1
let roundedValue = round(slider.value / step) * step
slider.value = roundedValue
print(roundedValue)
}
}
The basic idea is this:
--> Every time the screen needs to redraw (this will happen at max around 60 frames per second taking into consideration this is fps rate), we get a chance to perform function.
--> to do so, we add the displayLink to the Run Loop. (Run lopp processes input/ refreshes UI and time slices)
--> NOTE This method wont be called if there is no redraw needed on the screen. This is not a timer per say that fires periodically. It fires when redraw is needed. In Sliders case, we want this to fire when we move slightest of the slider too.
For more info on how it actually works try it out and see the apple documentation. Make sure to invalidate before deinitializing the ViewController.
Figured out the answer, thank to everyone for the help and suggestions:
override func viewDidLoad()
{
super.viewDidLoad()
NSTimer.scheduledTimerWithTimeInterval(0.02, target: self, selector: #selector(sendSliderPosition), userInfo: nil, repeats: true)
}
#IBAction func sliderValueChanged(sender: AnyObject)
{
let step: Float = 1
let roundedValue = round(slider.value / step) * step
slider.value = roundedValue
if sliderArray.first != slider.value
{
sliderArray.insert(slider.value, atIndex: 0)
}
}
func sendSliderPosition()
{
if sliderArray.count > 1
{
let end1 = sliderArray.count-2
print(sliderArray[end1])
sliderArray.removeLast()
}
}
Explanation:
If the new slider value is different to the one already in the array then add it to the array at the start. Use an NSTimer to repeatedly call the sendSliderPosition function from viewDidLoad. The function will only be performed if there is more than one element in the array. If there is, print the element before the last one and remove the last. This always ensures that there is one element in the array so the function does not always run and that the element printed is the most recent one that hasn't already been printed.
Sorry if this is a really basic questions, but I can’t seem to work it out, so I thought I would ask the experts.
I’ve got a timer for my project that counts down and updates a label according to what is stored in an array.
var array : String[]()
var x = 0
#IBAction func playBtnPressed(sender: UIButton)
{
timer = NSTimer.scheduledTimerWithTimeInterval(60, target: self, selector: #selector(PlayVC.update), userInfo: nil, repeats: true)
}
func update()
{
if x < array.count {
let item = array[x]
aLbl.text = array.itemTitle
x += 1
}
}
My problem is that the text is only updated after the first countdown and 60 seconds is a long time to wait lol.
I would actually like the first String in my array to appear as soon as the button is tapped.
Is there a way to set the text at the very beginning of the countdown?
Thank you for your help :)
So you want to update a label every minute. And you also want to update it immediately after the button is pressed. Hopefully I didn't misunderstand the question.
It's actually as easy as adding this line before the timer = NSTimer ... line:
update()
Note that your current code can cause two or more timers to be created and run when the button is pressed more than once. You might not want this.
To stop the timer when the button is pressed a second time, do this:
if timer == nil {
timer = NSTimer.scheduledTimerWithTimeInterval(60, target: self, selector: #selector(PlayVC.update), userInfo: nil, repeats: true)
} else {
timer.invalidate()
timer = nil
}
To do nothing when the button is pressed a second time jus remove the else part.
I got a button with a LongPressureGesture, and i would like to have a small ProgressView on top of this button as visual feedback for the user that the longPressureGesture is recognized.
I'm stuck on how to detect the beginning of the longPressure and the duration of the longPressure to be able to set the setProgress() on my ProgressView.
EDIT: So i inspired myself from the answers, thank you. Here is what i made. Feel free to comment the following code, maybe there is a better solution.
private var lpProgress:Float = 0
private var startTouch: NSTimer!
#IBAction func pauseJogButtonTouchDown(sender: AnyObject) {
self.startTouch = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "updateLPGestureProgressView", userInfo: nil, repeats: true)
}
func updateLPGestureProgressView() {
self.lpProgress += 0.1
self.lpGestureProgressView.setProgress(self.lpProgress, animated: true)
if self.lpProgress >= 1 {
self.startTouch.invalidate()
self.pauseBarButton.hidden = true
self.lpGestureProgressView.setProgress(0.0, animated: false)
self.toolbarHomeMadeView.hidden = false
self.switchToState(.Paused)
}
}
#IBAction func pauseJogButtonTouchUpInside(sender: AnyObject) {
self.lpProgress = 0
self.startTouch.invalidate()
self.lpGestureProgressView.setProgress(0.0, animated: false)
}
You do not need the LongPressureGesture in this case.
Use "Touch Down" IBAction of UIButton to start NSTimer, and "Touch Up Inside" to stop timer and check if the delay was right.
ProgressView you can fill by timer progress.
Set up an NSTimer on touchesBegan.
At the same time start your animation to animate the view.
When touchesEnded is triggered then stop the animation if the NSTimer has not triggered yet and cancel the timer.
When the timer finishes run your desired action.
Long Press isn't really designed for this sort of thing.
I made a practice project in Swift to learn how NSTimer works. There is one button to start the timer and one button to invalidate it. It works fine when I tap each button once. However, when I tap the start timer button multiple times, I am no longer able to invalidate it.
Here is my code:
class ViewController: UIViewController {
var counter = 0
var timer = NSTimer()
#IBOutlet weak var label: UILabel!
#IBAction func startTimerButtonTapped(sender: UIButton) {
timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "update", userInfo: nil, repeats: true)
}
#IBAction func cancelTimerButtonTapped(sender: UIButton) {
timer.invalidate()
}
func update() {
++counter
label.text = "\(counter)"
}
}
I have seen these questions but I wasn't able to glean an answer to my question from them (many are old Obj-C pre-ARC days and others are different issues):
NSTimer() - timer.invalidate not working on a simple stopwatch?
Using an NSTimer in Swift
NSTimer doesn't stop
Unable to invalidate (Stop) NSTimer
NSTimer doesn't stop with invalidate
Can't invalidate, stop countdown NSTimer - Objective C
IOS: stop a NSTimer
You can add timer.invalidate() before starting a new timer in startTimerButtonTapped if you want to reset the timer each time the "start" button is tapped:
#IBAction func startTimerButtonTapped(sender: UIButton) {
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "update", userInfo: nil, repeats: true)
}
I was going to update with an explanation but #jcaron already did it in the comment, so I'm just quoting his text, no need to change it:
Every time you tap on the "Start Timer" button, you create a new timer, while leaving the previous one running, but with no reference to it (since you've overwritten timer with the new timer you just created). You need to invalidate the previous one before you create the new one.
I would like to suggest you to set timer to nil when press on cancel button.
And don't forget to set counter =0
When invalidating the Timer.