NSTimer is being triggered only one time after invalidating and reinitializing - ios

I have the following function setPath() which is called whenever the user taps on a button:
var loadSuccessfulTimer: Timer? = nil
func setPath(index: Int) {
self.loadSuccessfulTimer?.invalidate()
self.loadSuccessfulTimer = nil
print("setting path")
self.pause(releaseAudioSession: false)
self.reset(index: index)
let song = DataManager.getInstance().getQueueSong(index: index)
superpowered.setPath(song.url, true)
self.loadSuccessfulTimer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(checkLoadSucess), userInfo: nil, repeats: true)
self.loadSuccessfulTimer!.fire()
}
#objc func checkLoadSucess() {
let status = self.superpowered.getLoadSuccessful()
print(status)
if(status != -1) {
self.loadSuccessfulTimer?.invalidate()
self.loadSuccessfulTimer = nil
if(status == 1){
print("success")
} else if(status == 2){
print("failed")
}
}
}
So whenever the setPath() function gets called, I want the timer to trigger every 0.2 seconds to check on a the value of status = self.superpowered.getLoadSuccessful(). The variable status would have values -1, 1, 2 I want to stop the timer when it's either 1 or 2. However, the following scenario is happening:
On the first tap it is working as expected and printing the following:
setting path
-1
-1
-1
-1
-1
-1
1
success
On the second tap the timer is only triggered one time (I guess it is from the .fire()) and the following is being printed:
setting path
-1
I tried to look up what might be going wrong, but all I could see was this is the recommended way of using a Timer.
Update
The second time setPath() is being called from DispatchQueue.global(), maybe this is related to the issue.

NSTimer requires run loop to work properly. The main queue has a run loop for free, for other queues you need to make sure run loop is available.
Make sure you read the documentation: https://developer.apple.com/documentation/foundation/nstimer
Also, remember to invalidate NSTimer object from the same run loop (thread) is has been created (scheduled) from.

Related

How do you make a timer controller with a condition?

I'm a beginner in Swift and have a task to change the bottom sheet message when the process of the app doesn't work in three minutes. So, the message will change from "available" to "not available" if the process does not work.
I found code syntax like:
Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
What I think:
var waktu = 0
Timer.scheduledTimer(withTimeInterval: 180.0, repeats: false) {
if waktu == 180 {
timer.invalidate()
//run the change message function
}
}
You can add observer on your property, so it will invalidate the timer when condition met, like so:
var waktu = 0 {
didSet {
if waktu == 180 {
timer.invalidate()
}
}
}
Your code creates a timer that will fire once in 3 minutes.
You’re using a variation on the timer method you list, scheduledTimer(withTimeInterval:repeats:block:)
That variant takes a closure instead of a selector. Then you’re using “trailing closure syntax” to provide that closure in braces outside the closing paren for the function call. That’s all good.
However, you define a variable waktu And give it a starting value of 0. You don’t show any code that will change that value, so your if statement if waktu == 180 will never evaluate to true and run your change message function. You need to update that if statement to determine if “the process of the app works”, whatever that means. (Presumably you have some definition of what your app working/not working means, and can update that if statement accordingly.)

Multiple timers at once ios

I am making an app where the user can have multiple timers going at once and see them in a list view.
I am aware that there are 2 main options for working out time:
Subtract the date started from current date (current date-start date)
OR
Use an NSTimer and take away 1 second every second from each active timer.
I have previously been using the latter, but having looked around the internet I am starting to think that the data one may be better.
Please could you let me know which you think is best to use, and if you chose the first one (dates), please could you provide some sample code on how to use it.
You can Use an NSTimer and take away 1 second every second from each active timer. You can use this class.
class CustomTimer {
typealias Update = (Int)->Void
var timer:Timer?
var count: Int = 0
var update: Update?
init(update:#escaping Update){
self.update = update
}
func start(){
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerUpdate), userInfo: nil, repeats: true)
}
func stop(){
if let timer = timer {
timer.invalidate()
}
}
/**
* This method must be in the public or scope
*/
#objc func timerUpdate() {
count += 1;
if let update = update {
update(count)
}
}
}
To use multiple timer you can create multiple instance of CustomTimer, Example Code:
let timer1 = CustomTimer { (seconds) in
// do whatever you want
}
timer1.start()
let timer2 = CustomTimer { (seconds) in
// do whatever you want
}
timer2.start()
NOTE:
timerUpdate method will be called exactly at 1 second interval. to keep some space for function execution we can set interval to 0.9 or 0.95 according to time taken by execution.
You use both. You have one Timer that repeats every second. The handler for the Timer then iterates through your list of start dates for each of the user's timers and you update the display for each based on the current date.

Swift: How to make a method to be called every 10 seconds

In my main ViewController ViewDidLoad, I have the following code that calls the "tick" method every second.
self.timerTick = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(ViewController.tick), userInfo: nil, repeats: true)
In the "tick" method, I check if a boolean value is true every second, and if true, I need to call a second method called SpeakText(), but I want to call the SpeakText() only once every 10 seconds. How do I do this?
Create a class property to keep track of the time since the last SpeakText. Update it each time tick is called, and reset it to 0 when SpeakText is called:
var secondsSinceSpeakText = 10
func tick() {
secondsSinceSpeakText += 1
if boolFlag && secondsSinceSpeakText >= 10 {
SpeakText()
secondsSinceSpeakText = 0
}
}
Note: Starting secondsSinceSpeakText with an initial value of 10 will allow the first SpeakText to happen immediately.
Just create another timer for speaktext when and when you call your speak text function fire it. If its under 10 seconds return false

Displaying texts one by one in same UILabel

I have a UILabel in my app which display chat messages. These chat messages are hardcoded as an array of strings. What i need is to display these messages one by one on a UILabel but i need to have a certain 2 second delay between switching messages as user need to read each message.
What i have implemented is the following code:
for chatText in chatDialogue{
dispatch_async(dispatch_get_main_queue(), {
chatLabel.text=chatText as? String
})
println(chatText)
NSThread .sleepForTimeInterval(2)
}
here we have an array of chatDialogues
["Hello","How are you?","Can you say DIAS","Wait Please"]
Now i need these to be displayed on the same label but with a say delay between changing messages.
The above implementation when executed showed only the last message on the loop.
Set up a NSTimer which fires every 2 seconds and stops when reaching the last text:
// instance variables
let chatDialoges = ["Hello","How are you?","Can you say DIAS","Wait Please"]
var textIndex = 0
var timer: NSTimer?
// ...
// somewhere where the timer should start
self.timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: "update", userInfo: nil, repeats: true)
//
func update() {
self.textLabel++
if self.textLabel == chatDialoges.count {
self.timer?.invalidate()
return
}
chatLabel.text = chatDialoges[self.textIndex]
}

Swift - slowing down "too fast" animation (UIImage updates) -- aka is NSTimer the only option here?

Newbie to IOS programming - learning through Swift. I'm writing a simple "slot machine / dice game".
I'm trying to show the user a flashing sequence of rolls before the "actual" roll appears.
func doFancyDiceRoll() {
for x in 1...100 {
Die1.image = PipsImg[RollOne()]
Die2.image = PipsImg[RollOne()]
Die3.image = PipsImg[RollOne()]
}
}
Die1, etc., are defined as generic UIImage views.
In any case, I'm not seeing the 100x iterations, just the images at the end of the loop. I'm assuming that either it redraws "too fast" or that IOS is trying to be smart, and only draws the last images so as to conserve resources.
I'll wildly guess that I need to either implement some kind of delay here, or, IOS needs to be told to explicitly draw out my images, and not try to outthink my intent.
For the delay, I've seen something about NSTimer, but nothing I saw seems to simply say something like "pause for .05" second, and the whole construct was unclear as they were ObjC examples/conversions.
(Note: I've simplified things here --- normally, I would store the value of RollOne() so I can use it later. I also would like to make an array (or collection?) like Die[1].image, but that is another question.)
========== Edit =======
OK, so I'm following up with more of my original code, merged in with that of #doctordoder so we can discuss a bit better. Hopefully that is kosher. (If this appended question is too long, please advise me on the best way to post a lengthy follow-up directly.)
import UIKit
class ViewController: UIViewController {
//( stripping out stuff unneeded for discussion )
// refers to same label below - works but kosher??
#IBOutlet var btnRoll_x: UIView
#IBAction func btnRoll(sender: AnyObject) {
triggerRoll()
}
var timer : NSTimer? = nil
var rolls : Int = 0
func triggerRoll() {
//hiding is bad UI, but until i know how to "disable & dim"
btnRoll_x.hidden = true
timer = NSTimer.scheduledTimerWithTimeInterval(0.10, target: self, selector: "doFancyDiceRoll", userInfo: nil, repeats: true);
}
func doFancyDiceRoll() {
Die1.image = PipsImg[randomInt(6)]
Die2.image = PipsImg[randomInt(6)]
Die3.image = PipsImg[randomInt(6)]
if (++rolls > 10)
{
timer?.invalidate()
timer = nil
rolls = 0 // DCB added this presumed missing line
btnRoll_x.hidden = false //again, need to do this better
}
}
}
Hopefully, my implementation of the code is what would have been intended. I made some minor adjustments for (hopeful) clarity.
Good news is I have working code. I have just enough understanding to get in place, but I'm fuzzy on some details.
Here is what I (think I) know...
We declare an NSTImer object, and a roll counter at the main level of the class. I note that in my original version, I had the roll counter scoped within the rolling function itself. Took me a while to understand why it could not live in the DiceRoll loop itself, but now I do. I'm going to express it poorly, but since the timer is going to call DiceRoll multiple instances, it needs to live outside the function.
The button btnRoll gets touched, and invokes triggerRoll().
To prevent the user from touching the button while we are in progress, which put us into a state where the roll counter never got to zero, I hide the button. (I'll figure how to properly put in in disabled state later.)
The timer is set. It fires every .1 second (within limits), and is set to repeat. (until .invalidate ). And it "calls" the function doFancyDiceRoll via the selector: attribute.
So, the big change as previously noted is that doFancy..Roll() no longer loops. It excites a single instance up updating the images. It checks the counter, and if we reach the limit, we kill the timer, which stops the timer (invalidate). (And I unhide the button, making it available again.)
So, a few things I am concerned/wondering about: I get the value of timers for other things that need to happen periodically (take health away every second, check a GPS position every 10 seconds, etc.). It's seems a odd construct to force a screen refresh.
Frankly, I would have expected to see see something like this:
func doFancyDiceRoll() {
for x in 1...100 {
Die1.image = PipsImg[RollOne()] // and 2 and 3 of course.....
VIewController.forceRedraw <<=== something like this, or maybe
ViewController.wait(.05) <<== this?? I dunno ;-)
}
}
instead we end up with about 20 extra lines or so. I'd be interested in knowing if there other approaches that could work keeping the loop intact.
Anyway, assuming this is the one true way to go, I guess my followup to this is how do I pass parameters, since this is not a "real" function call. Trying
selector: "doFancyDiceRoll(40)"
was not objected to by the IDE, but failed in execution.
I had exactly same problem back in days, entire loop is finished before the view is refreshed as #doctordoder mentioned. Solved with using NSTimer
var rollCount = 0
let arrayOfImages = ["image01", "image02", "image03"]
var timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("doFancyDiceRoll"), userInfo: nil, repeats: true)
func doFancyDiceRoll() {
if rollCount == 100 {
timer.invalidate
rollCount = 0
{
else {
//get images from array arrayOfImages[rollCount]
rollCount++
}
}
there could be typos, since I have no Xcode right now.
I have basically the same answer as above :(, but I thought I'd post it anyway.
var timer : NSTimer? = nil
var rolls : Int = 0
func doFancyDiceRoll() {
timer = NSTimer.scheduledTimerWithTimeInterval(0.10, target: self, selector: "roll", userInfo: nil, repeats: true);
}
func roll() {
println("rolling")
Die1.image = PipsImg[RollOne()]
Die2.image = PipsImg[RollOne()]
Die3.image = PipsImg[RollOne()]
if (++rolls > 100)
{
timer?.invalidate()
timer = nil
}
}
Rather than NSTimer and invalidating, you can use dispatch_after to do the work for you.
func rollDice(howManyTimes: Int) {
die1.image = PipsImg[RollOne()]
die2.image = PipsImg[RollOne()]
die3.image = PipsImg[RollOne()]
if howManyTimes > 0 {
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(Double(NSEC_PER_SEC) / 10.0))
dispatch_after(delayTime, dispatch_get_main_queue()) {
self.rollDice(howManyTimes - 1)
}
}
}
This will run the code for the number of times specified, delaying each time by 0.1 seconds. It works like this: First it sets the images on each die, then, if there are more iterations, it does a dispatch_after to call itself with rollDice(howManyTimes - 1)
With this, you don't need to maintain a NSTimer and it is pretty self contained.

Resources