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.
Related
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'm having a problem with my timer on swift. When I press the play button once it works fine and adds one second every second but when I play it twice it adds 2 seconds every one second and so on. This is my code.
var timer = NSTimer()
var time = 0
var minute = 0
#IBOutlet var timerLabel: UILabel!
func increasedTimer()
{
time++
timerLabel.text = "\(minute)" + "m " + "\(time)" + "s"
if time == 59
{
minute++
time = -1
}
}
#IBAction func play(sender: AnyObject)
{
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("increasedTimer"), userInfo: nil, repeats: true)
}
#IBAction func reset(sender: AnyObject)
{
timer.invalidate()
time = 0
minute = 0
timerLabel.text = "0m 0s"
}
#IBAction func pause(sender: AnyObject)
{
timer.invalidate()
}
Every time you tap play you create and start an additional timer.
You have a few options to fix this.
From a user experience point of view, you need to enable/disable your three buttons (Play, Pause, Stop) appropriately. It makes no sense that the user can tap Play a 2nd time while the timer is going. And of course the Pause and Stop buttons shouldn't be enabled until Play has been tapped.
So start by fixing the user interface so the buttons make sense. Once that is done, you won't have the problem in your current code since the user won't be able to tap Play twice without first pausing or stopping.
In the short term, every time you play put another timer.invalidate() before starting a new timer. Definitely better to disable the play button.
button.setBackgroundImage(backgroundImage1, forState: .Normal)
button.setBackgroundImage(backgroundImage2, forState: .Highlighted)
button.setBackgroundImage(backgroundImage2, forState: .Disabled)
Examples of things you can do. You can supply different images for a greyed out play button, a selectable play button. Etc. Then change the forState. More options available then shown.
Or you can just change the text shading in the button with:
button.enabled = true
button.enabled = false
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.
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.
So I am using an NSTimer to let the user know the app is working. The progress bar is set up to last 3 seconds, but when running, it displays in a 'ticking' motion and it is not smooth like it should be. Is there anyway I can make it more smooth - I'm sure just a calculation error on my part....
If anyone could take a look that would be great. Here is the code:
import UIKit
class LoadingScreen: UIViewController {
var time : Float = 0.0
var timer: NSTimer?
#IBOutlet weak var progressView: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
// Do stuff
timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector:Selector("setProgress"), userInfo: nil, repeats: true)
}//close viewDidLoad
func setProgress() {
time += 0.1
progressView.progress = time / 3
if time >= 3 {
timer!.invalidate()
}
}
}
Edit: A simple 3 second UIView animation (Recommended)
If your bar is just moving smoothly to indicate activity, possibly consider using a UIActivityIndicatorView or a custom UIView animation:
override func viewDidAppear(animated: Bool)
{
super.viewDidAppear(animated)
UIView.animateWithDuration(3, animations: { () -> Void in
self.progressView.setProgress(1.0, animated: true)
})
}
Make sure your progressView's progress is set to zero to begin with. This will result in a smooth 3 second animation of the progress.
Simple animated progress (Works but still jumps a bit)
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIProgressView_Class/#//apple_ref/occ/instm/UIProgressView/setProgress:animated:
func setProgress() {
time += 0.1
progressView.setProgress(time / 3, animated: true)
if time >= 3 {
timer!.invalidate()
}
}
Option with smaller intervals. (Not recommended)
Set your timer to a smaller interval:
timer = NSTimer.scheduledTimerWithTimeInterval(0.001, target: self, selector:Selector("setProgress"), userInfo: nil, repeats: true)
Then update your function
func setProgress() {
time += 0.001
progressView.setProgress(time / 3, animated: true)
if time >= 3 {
timer!.invalidate()
}
}
For continues loader
timer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(setProgress), userInfo: nil, repeats: true)
and
func setProgress() {
time += 0.001
downloadProgressBar.setProgress(time / 3, animated: true)
if time >= 3 {
self.time = 0.001
downloadProgressBar.progress = 0
let color = self.downloadProgressBar.progressTintColor
self.downloadProgressBar.progressTintColor = self.downloadProgressBar.trackTintColor
self.downloadProgressBar.trackTintColor = color
}
It's hard to say exactly what the problem is. I would like to see the output if you put a print line in setProgress to print a timestamp. Is it actually firing every tenth of a second? My guess is that it is not.
Why not? Well, the timer schedules a run loop task in the main thread to execute the code in setProgress. This task cannot run until tasks in front of it in the queue do. So if there are long running tasks happening in your main thread, your timer will fire very imprecisely. My first suggestion is that this is perhaps what is happening.
Here is an example:
You start a timer to do something every second.
Immediately after, you start a long running main thread task (for example, you try to write a ton of data to a file). This task will take five seconds to complete.
Your timer wants to fire after one second, but your file-writing is
hogging the main thread for the next four seconds, so the timer can't fire
for another four seconds.
If this is the case, then to solve the problem you would either need to move that main thread work to a background thread, or else figure out a way to do it while returning to the run loop periodically. For example, during your long running main thread operation, you can periodically call runUntilDate on your run loop to let other run loop tasks execute.
Note that you couldn't just increment the progress bar fill periodically during the long running main thread task, because the progress bar will not actually animate its fill until you return to the run loop.
What about proper way for animating changes: animateWithDuration:animations: or CABasicAnimation. You can use this for creating smooth animations