I have an NSTimerand I am using the method scheduledTimerWithTimeInterval but I would like the time interval it takes as a parameter to be a function, not an explicit value. How could I do this?
My code looks like this:
timer = NSTimer.scheduledTimerWithTimeInterval(time(), target: self, selector: "doStuff", userInfo: nil, repeats: true)
The compiler complains with the first parameter time()
edit: the error is "extra argument 'selector' in call"
I'm not sure exactly why you're getting that error, but time() is certainly not what you want for your time interval. That's supposed to be the number of seconds between firings of the timer. For something like a repeating animation timer that will typically be something like 1/30.0 or 1/60.0.
The time function (which, by the way, should take an argument) returns an integer that's the number of seconds since the "epoch." On Mac OS X the epoch is 12:00 AM, UTC of January 1, 2001.
Edit: It sounds from your comment like you've defined your own time function. I would first of all, change the name of your function to (say) timeInterval so it can't possibly collide with the system's time function. And I would make sure your function returns an NSTimeInterval, as opposed to a Float or some other number type, like:
func timeInterval() -> (NSTimeInterval) {
return 1/60.0
}
var timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval(), target: self, selector: "doStuff", userInfo: nil, repeats: true)
Related
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.)
I have a timer, which is a singleton, that repeatedly fires every second. I allow the user to pause the timer as well as resume. I am keeping track of the start date of the timer, and I am subtracting any pauses from the elapsed time.
Unfortunately, I can't seem to fix an intermittent issue where pausing and resuming the timer causes a skipping of one second.
For instance, in the following code block, I start the timer and print the seconds:
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0
10.0
11.0
12.0
13.0
14.0
15.0
16.0
17.0
18.0
19.0
In the following code block, I resume the timer, printing the seconds again. However, as you can see, the 20th second has not been printed:
21.0
22.0
23.0
24.0
25.0
26.0
I cannot seem to figure out where I am losing the second. It does not happen with each pause and resume cycle.
The properties that I am using to keep track of the aforementioned are as follows:
/// The start date of the timer.
private var startDate = Date()
/// The pause date of the timer.
private var pauseDate = Date()
/// The number of paused seconds.
private var paused = TimeInterval()
/// The number of seconds that have elapsed since the initial fire.
private var elapsed = TimeInterval()
I start the timer by creating the timer and setting the start date:
/// Starts the shower timer.
func startTimer() {
// Fire the timer every second.
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
// Set the start time of the initial fire.
startDate = Date()
}
If the user pauses the timer, then the following method executes:
/// Pauses the shower timer.
func pauseTimer() {
// Pause the timer.
timer?.invalidate()
// Set the timer to `nil`, according to the documentation.
timer = nil
// Set the date of the pause.
pauseDate = Date()
}
Then, the following method executes when the user resumes the timer:
/// Resumes the timer.
func resumeTimer() {
// Recreate the timer.
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
// Add the number of paused seconds to the `paused` property.
paused += Date().timeIntervalSince(pauseDate)
}
The following method, which is called by the method that executes when the timer fires, sets the number of elapsed seconds since the initial fire, less the sum of any pauses:
/// Sets the number of elapsed seconds since the timer has been started, accounting for pauses, if any.
private func updateElapsedTime() {
// Get the date for now.
let now = Date()
// Get the time that has elapsed since the initial fire of the timer, and subtract any pauses.
elapsed = now.timeIntervalSince(startDate).rounded(.down).subtracting(paused.rounded(.down))
}
Finally, the following method is the Selector that executes when the timer fires:
/// Updates the number of elapsed seconds since the timer has been firing.
#objc private func updateElapsedSeconds() {
// Configure the elapsed time with each fire.
updateElapsedTime()
// Post a notification when the timer fires, passing a dictionary that includes the number of elapsed seconds.
NotificationCenter.default.post(name: CustomNotification.showerTimerFiredNotification, object: nil, userInfo: nil)
}
What am I doing incorrectly to cause a missing second intermittently?
So the issue here is that Timer is not accurate in this way. Or rather, its timekeeping is reasonably accurate, but the actual rate of firing has some variance as it is dependent on the runloop.
From the documentation:
A timer is not a real-time mechanism; it fires only when one of the
run loop modes to which the timer has been added is running and able
to check if the timer’s firing time has passed.
To show this, I got rid of all of the rounding in your code and printed the output (you don't even need to pause to see this happen). Here is what this variance looked:
18.0004420280457
19.0005180239677
20.0004770159721
21.0005570054054
21.9997390508652
23.0003360509872
24.0003190040588
24.9993720054626
25.9991790056229
Sometimes it fires particularly late and this causes the whole thing to get thrown off. The rounding doesn't help because you are still depending on the timer for the actual reference time and eventually it will be off by more than a second.
There are a few ways to fix the situation here depending on what exactly you are trying to accomplish. If you absolutely need the actual time, you can adjust the timer to fire at fractions of a second and instead use that output to estimate the seconds a little more accurately. This is more work and will still not be totally right (there will always be a variance).
Based on your code, it seems like simply incrementing a number based on the timer should be enough to accomplish your goal. Here is a simple modification to your code making this work. This will count up simply and never skip a second in the count whether you pause or not:
/// The number of seconds that have elapsed since the initial fire.
private var elapsed = 0
private var timer: Timer?
/// Starts the shower timer.
func startTimer() {
elapsed = 0
// Fire the timer every second.
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
}
/// Pauses the shower timer.
func pauseTimer() {
// Pause the timer.
timer?.invalidate()
// Set the timer to `nil`, according to the documentation.
timer = nil
}
/// Resumes the timer.
func resumeTimer() {
// Recreate the timer.
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
}
/// Sets the number of elapsed seconds since the timer has been started, accounting for pauses, if any.
private func updateElapsedTime() {
// Get the time that has elapsed since the initial fire of the timer, and subtract any pauses.
elapsed += 1
// debug print
print(elapsed)
}
/// Updates the number of elapsed seconds since the timer has been firing.
#objc private func updateElapsedSeconds() {
// Configure the elapsed time with each fire.
updateElapsedTime()
// Post a notification when the timer fires, passing a dictionary that includes the number of elapsed seconds.
NotificationCenter.default.post(name: CustomNotification.showerTimerFiredNotification, object: nil, userInfo: nil)
}
I am trying to build a simple single view app which runs an infinite slide show of a selection of images with a time delay between each image change.
The code I wrote for this is below. I tried to put this into viewDidLoad and viewDidAppear but the screen remained blank which I guess is because the function never finishes due to the infinite loop.
I learnt a bit of Python before iOS and with tkinter, your code would go into the mainloop. But I am not quite sure how to do the equivalent in Swift
Could someone please explain why I am having this problem and how to do this in Swift. Thanks.
var arrayimages: [UIImage] = [UIImage(named: "charizard")!,UIImage(named:"Flying_Iron_Man")!]
var x: Int = 0
var images: UIImage
let arraycount = arrayimages.count
repeat{
images = arrayimages[(x % arraycount)]
sleep(1)
slideshow.image = images
x++
} while true
NB: slideshow is an image view outlet.
You're looking for NSTimer
let timer = NSTimer.scheduledTimerWithTimeInterval(
1.0, target: self, selector: Selector("doYourTask"),
userInfo: nil, repeats: true)
The first argument is how frequently you want the timer to fire, the second is what object is going to have the selector that gets called, the third is the selector name, the fourth is any extra information you want to pass as a parameter on the timer object, and the fifth is whether this should repeat.
If you want to stop the code at any future point:
timer.invalidate()
Create a repeating NSTimer:
var timer = NSTimer.scheduledTimerWithTimeInterval(2.0,
target: self,
selector: "animateFunction:",
userInfo: nil,
repeats: true)
Then write a function animateFunction:
func animateFunction(timer: NSTimer)
{
//Display the next image in your array, or loop back to the beginning
}
Edit: Updated for modern Swift versions: (>= Swift 5)
This has changed a lot since I posted this answer. NSTimer is now called Timer in Swift, and the syntax of the scheduledTimer() method has changed. The method signature is now scheduledTimer(timeInterval:target:selector:userInfo:repeats:)
Also, the way you create a selector has changed
So the call would be
var timer = Timer.scheduledTimer(timeInterval: 2.0,
target: self,
selector: #selector(animateFunction(_:)),
userInfo: nil,
repeats: true)
And the animateFunction might look like this:
func animateFunction(timer: Timer)
{
//Display the next image in your array, or loop back to the beginning
}
I am trying to run subsequent Timer events with WKInterfaceTimer & NSTimer, the problem is that I cannot figure out a way to make more than two subsequent calls with one NSTimer object. Basically, I would like run timer to complete then fire up the next.
Here's some sample code that hopefully explains my idea a little better....
1) I am firing off the first timer in awakeWithContext:
func initalTimer() {
let timer1String = NSMutableAttributedString(string: "Lap1")
runStatusLabel.setAttributedText(timerString)
myTimer = NSTimer.scheduledTimerWithTimeInterval(duration, target: self, selector: Selector("timerDone"), userInfo: nil, repeats: false)
runTimer.setDate(NSDate(timeIntervalSinceNow: duration))
runTimer.start()
}
NOTE: Everything works great at this point, then the tiemrDone function is called where I then fire off another timed event.
2)
func timerDone() {
//print("Done")
elapsedTime = 0.0
myTimer!.invalidate()
startTime = NSDate()
timeRunning = false
// Call second timed event
timer2() // just another NSTimer / WKInterfaceTimer function
}
"Stacking" the functions with a completionHandler does not seem to help OR most likely I am doing something wrong...
func execute_Timers(timeInterval: NSTimeInterval, completionHandler: (success: Bool, error: String?) -> Void ) -> Int {
// Code below never gets executed
}
I haven't tested this, and it is just a guess: When your timerDone() method is called, you invalidate the timer. Therefore it doesn't "complete," so your completion routine isn't called. When your timer completes, it gets invalidated anyway, so the call should not be needed. Try removing:
myTimer!.invalidate()
and see what happens.
Thanks for the reply, and you are quite correct - I do not need to call myTimer!.invalidate(). The solution that worked for me was to have different timerDone methods and conditionaly call the next time method.
-Paul
I've written a test that checks if some value will go down to zero until 5 seconds. Every each second NSTimer fires a method that decrements this value by one.
It looks like this:
let foo = SomeObject(initWithTime: 5)
let expectation = expectationWithDescription("Ohai")
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: foo, selector: Selector("decrement"), userInfo: nil, repeats: true)
XCTAssertTrue(foo.isFinished, "Foo is finished, so it should be true")
expectation.fulfill()
waitForExpectationsWithTimeout(5.0, handler: nil)
isFinished sets to true when foo reaches zero. Timer works fine, but expectation doesn't wait for 5 seconds, just crashes instantly. What's happening?
It seems that your XCTAssertTrue fail immediately since foo is probably not finished when it is called immediately.