How to postpone hiding view when user clicks button again? - ios

If user clicks button I hide view after 3 seconds using DispatchQueue.main.asyncAfter. However if user again clicks that button I wan't to postpone hiding activity for next more 3 seconds so previous call of hiding will be ignored.
I have tried following code but it hides view after first 3 seconds.
class MyView: UIView {
private var hideControls: DispatchWorkItem?
func displayControls() {
isHidden = false
hideControls?.cancel()
hideControls = DispatchWorkItem {
print("displayControls: DispatchWorkItem called ")
self.isHidden = true
}
if let hideControls = hideControls {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3), execute: hideControls)
}
}
}

If you want to ignore the previous work you can work with Timer, add the property timer,
var timer : Timer?
and on your method for hidden use this.
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false, block: {[weak self] (_) in
#add the action that you want
self?.isHidden = true
})
Br.

Instead of a dispatch work item or GCD, use a Timer:
The Timer is an instance property and is an Optional. It is initially nil.
The Timer is a one-time timer for 3 seconds, and its action method hides the button.
When the user presses the button, safely invalidate the Timer with self.timer?.invalidate() and replace it with a new scheduled Timer — self.timer=...
If you think about it, you will see that this solves the problem. No matter whether a timer was already running, pressing the button now means "regardless of what I may have said in the past, three seconds from now is when the button should be hidden."
I use this technique all the time. For instance, I have a game where the user is to be penalized for each ten seconds that goes by without making a move. So if the user moves, I replace the timer with a new ten-second timer. If the user doesn't within the (new?) ten-second period, the timer fires and the penalty is applied. (It happens that I then make a new timer, but that wouldn't be the case in your example.)

Related

How to restart or stop a Timer in Swift without destroying it?

I'm developing an iOS application using a bluetooth device with a button that is communicating with the iPad. Basically I want a help request to be issued when the button is held for 3 seconds or longer.
From all the documentation I found, I couldn't find a way to stop a Timer without invalidating it, with the invalidate() method. From Apple's documentation:
The run loop then removes the timer (and the strong reference it had to the timer), either just before the
invalidate()
method returns or at some later point. Once invalidated, timer objects cannot be reused.
So the idea in my code is that when the button is pressed, the boolean buttonWasHeld is set to true and a timer is fired. If the button is released, buttonWasHeld is set to false and, when the timer calls the handler it knows the button wasn't held long enough. Then if the button is pressed again within the 3 seconds, the timer is set over again.
Problem is: every button press makes a new timer, which means that repeatedly pressing the button will also issue the help request. Furthermore, all those timers are addressed by the same variable so I can't tell them apart.
Is there a way to uniquely tell what was the last timer created? Or an obscure way to pause/stop it?
Here's the piece of code controlling this feature:
var buttonTimer: Timer?
var buttonWasHeld: Bool = false
func didUpdateModule() {
// gpioListener takes a handler to be called whenever a button is
// pressed or released. isPushed is a self-explanatory boolean.
self.controller.gpioListener() { isPushed in
if isPushed {
self.buttonWasHeld = true
self.buttonTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { _ in
if self.buttonWasHeld {
// Issue a help request
self.delegate?.notifyDevice(message: .HELP)
print("Asking for help")
}
}
print("Button was pressed")
}
else {
self.buttonWasHeld = false
// Also tried "self.buttonTimer = nil" here. Didn't work
print("Button was released")
}
}
}
As usual, the answer was quite simple.
If the Timer is declared as a weak var, and not just var, only the weak instantiation will be invalidated. So the code should be:
weak var buttonTimer: Timer?
var buttonWasHeld: Bool = false
func didUpdateModule () {
(...)
else {
// This will only invalidate the current running timer,
// not the whole variable :)
self.buttonTimer.invalidate
// I removed buttonWasHeld, it's not necessary anymore ;)
print("Button was released")
}
}
}

Despite having a delay my function gets executed too quickly

This is my code:
let playableCards = self.allPlayableCardsViews[0].allSubviews.flatMap { $0 as? UIButton }
var counter: Double = 0
for card in playableCards{
UIView.animate(withDuration: 0.3, delay: TimeInterval(counter), options: .init(rawValue: 0), animations: {
card.alpha = 1.0
print("hello")
}, completion: nil)
counter += (3.7/Double(16))
}
Normally in the print line there is a function. This function gets called the amount of loops which of course is good. However I want to add the same delay that is having my card to fade in. Now my function gets called without the delay, causing 16 functions to execute at the exact same time, which is I think weird because I clearly added a delay. I do not want to use completion since the function needs to be executed at the exact same time as the card fades in. How can it be that the card is fading in one after another and the function(print in this example) gets called without delays?
I now see in my debug session 16 times "hello" while the cards are still fading in.
Thank you.
The animation is delayed, but the block can be called at any time to figure out what properties are being animated -- these don't need to be at the same time.
Use a timer to call your function at the same time as the animation will go off. If you want it to be triggered by the animation actually happening, you may be able to use key-value observing (KVO) on the card.alpha property.

How to make a timer work with a loop in swift?

I have multiple views each associated with its own timer. However, only one view is running a time each time. I am accessing each view in a for ... in loop and if the previous timer in the previous view has stopped, which I invalidate in the selector method, I want to fire the time for the next view.
the code looks like this:
var timer = NSTimer()
let timeInterval:NSTimeInterval = 1
var views = [TimerView]()
var timeCountDown: NSTimeInterval = 0
override func viewDidLoad() {
//viewDidLoad create the necessary UIView and fill the views array then pass them to a call to startTimer:
startTimer(views)
}
//startTimer function create the countdown timer
func startTimer(timerViews: [TimerView]){
for timerView in timerViews {
if !timer.valid { // a view with its associated timer start only when the previous timer has run its course and is invalidated
//creating a timer to associate with the current view
timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
target: self,
selector: "timerDidEnd:",
userInfo: timerView,
repeats: true)
}
}
}
//Callback function
func timerDidEnd(timer: NSTimer){
let timerView = timer.userInfo as! TimerView
timeCountDown = timeCountDown - timeInterval
timerView.timeLabel.text = timeToString(timeCountDown)
timerView.curValue = CGFloat(timeCountDown)
if timeCount <= 0.0 {
resetTimeCount()
}
}
func timeToString(time:NSTimeInterval) -> String {
let minutes = Int(time) / 60
let seconds = time - Double(minutes) * 60
return String(format:"%02i:%02i",minutes,Int(seconds))
}
func resetTimeCount(){
timer.invalidate()
}
With some print debugging for three views I am getting this output: "running the for loop", "calling view #1 by for loop", "calling the timer", "calling view #2 by for loop", "calling view #3 by for loop","countdown starting" ... That is the countdown only start after the for loop has terminated.
the problem I have is that the first timer run while the for loop calls all the views in my view array. How do I get the for loop to wait for the timer to be invalidated before it iterates to the next view?
What I am trying to achieve is this: the first view starts a countdown; when that countdown finished a second appears and run a countdown as well. Then the third view with a new countdown and so on until the last view. But it looks like the for loop in my code finish looping before the first timer associated with the first view in the collection actually starts
thanks
edit: I am wondering if the timer is not running on a different thread than the for loop?
Your code has a logic problem. It looks like you have a single instance variable, timer. Your for loop starts, and on the first pass, presumably timer is not valid. So you overwrite it with a new timer that IS valid. Now on the second pass through the array of timerViews, you check the same shared timer instance variable, but this time (And all subsequent times) timer is valid, so the body of the if statement doesn't fire.
If you really want a separate timer for each view then you will need to store all those timers. One way would be to have an array of timers that goes along with your array timerViews. Or you could create an array of tuples where each element contains a view and it's associated timer.
Stepping back from your problem, though, why do you want a separate timer for each field? Why not have a single timer, and each time it fires, loop through the array of fields and decide what you need to do with each one? You could have an array of structs that contains references to your views plus status information that lets your loop decide what to do with each one.

Swift: NSTimer automatically ends without counting down

Swift newbie here. I have two questions:
First, how do I create a timer which AUTOMATICALLY counts down when a ViewController scene is opened? My problem is that the NSTimer ENDS automatically when the scene is opened.
For instance, whenever I go to the said scene, the TimerLabel says: “Time’s Up!”
Before my second question, below is my tweaked code from: makeapppie.com/…/swift-swift-using-nstimer-to-make-a-timer…/
var timer = NSTimer()
let timeInterval:NSTimeInterval = 0.05
let timerEnd:NSTimeInterval = 120.0 //Timer should end in 120 seconds
var timeCount:NSTimeInterval = 0.0
func timerDidEnd(timer:NSTimer){
timeCount = timeCount - timeInterval
if timeCount <= 0 {
TimerLabel.text = "Time's Up!"
timer.invalidate()
}
}
func timeString(time:NSTimeInterval) -> String {
let minutes = Int(time) / 60
let seconds = time - Double(minutes) * 60
return String(format:"%02i:%02i",minutes,Int(seconds))
}
func StartTimer() { // Function called in viewDidLoad
if !timer.valid{
TimerLabel.text = timeString(timeCount)
timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: true)
}
}
My second question is this: I do not want to use any UISwitch to choose whether to have the timer counting up, or counting down. I need a COUNTING DOWN timer only which AUTOMATICALLY counts down when view is opened. How do I do that?
Please take note that my time format is: “minutes:seconds” as described in the timeString function.
Please help.
Welcome to SO. I think you're in over your head here.
A timer doesn't count up or count down. It fires on a regular interval. You write a method that gets called each time the timer fires.
Note that 0.05 is a pretty short interval for a timer. Timers are not super-accurate, so you might be trying to get too much out of them.
Your code is a confused mess. You start timeCount out at 0. Your timer method, that gets called repeatedly, is called timerDidEnd even though you want your timer to keep running. Your timerDidEnd method subtracts "timeInterval" (.05 seconds) from timeCount when the timer fires, which makes timeCount negative. You then check to see if timeCount is less than 0 (it will be the very first time through this code) and if it is, you invalidate the timer. You also never do anything to display the value of timeCount.
If you want code that counts down, you need to start out with a value that's larger than 0, and subtract a small amount from it each time the timer fires. Then you need to do something with that timeCount value. If you're not displaying it somewhere, it really isn't doing anything useful.
P.S. On an unrelated subject, the avatar pictures on SO are square. You should post a square avatar picture (or create a square crop to post.) When you post a rectangular picture it gets really stretched-out and looks bad.
My problem is that the NSTimer ENDS automatically when the scene is opened.
It sounds like your putting your NSTimer and the code that manages it in a view controller. In that case, it's not surprising that the timer goes away when that view controller does. If the timer should persist from one view controller to the next, then you should manage it from an entirely separate object from the view controller. Since you don't want the timer object to have to keep track of all the view controllers that could possibly be interested in it, have it post notifications that interested view controllers can listen to.

Make an animation loop endlessly until I press a button in Swift iOS. How?

Here's my code for the animation:
UIView.animateWithDuration(3,
animations: {self.redBar.center = CGPointMake(self.redBar.center.x + 600, self.redBar.center.y + 600)},
completion: nil)
I want to make it loop until I press a button. How?
Put the animation code inside of it's own function. Call that function in the completion block of the animation unless your button has been pressed.
When your button is pressed, set a boolean flag that keeps your animation from running again, then use this to cancel the animation that's in progress.
A nice solution here is to call your animation through a function accessed by a timer.
Here's a simple example of employing a timer:
How can I make a countdown with NSTimer?
Would probably be easiest to assign a boolean to stop the animation on button press. Assuming timer repeats was set to true (on declaration), now just call timer.invalidate() and your timer will no longer repeat calls to your animation.

Resources