How to use NSTimer to fire off multiple times? - swift

I have a RNG and want it to go off every three seconds. So far I have
var timer = NSTimer(timeInterval: 3, target: self , selector: randomnumbers, userInfo: nil, repeats: true)
func randomnumbers() {
var rockNamesArray:[String] = ["bird", "rock2", "rock3"]
var rockpos = Int(arc4random_uniform(UInt32(3)))
}
But I have a bunch of error messages and I'm not sure how to organize it.
EDIT
The error message in this code is telling me that it has an unresolved identifier "self" and all the other error s are just ones occuring because I have changed this code, like unresolved identifier rockNamesArray and rockpos which happen 4 times in three different lines of code.
EDIT2
As stated in the comment the above code is placed outside of a class which explains that self is not working. But how to address the timer routine in this case?

it has an unresolved identifier "self"
It sounds like the code you've provided is not part of an instance method. self is a keyword that refers to the object whose code is executing. If you don't have an object, there's no self, hence the error. To solve the problem, you could pass a pointer to some other object that has a randomnumbers selector in place of self.

You can not refer to self as a value when assigning an initial value to every stored property before the completion of the first phase of initialization in Swift.
As The Swift Programming Language says:
Class initialization in Swift is a two-phase process. In the first phase, each stored property is assigned an initial value by the class that introduced it. Once the initial state for every stored property has been determined, the second phase begins, and each class is given the opportunity to customize its stored properties further before the new instance is considered ready for use.
An initializer cannot call any instance methods, read the values of any instance properties, or refer to self as a value until after the first phase of initialization is complete.

Try this one:
var timer = NSTimer(timeInterval:3, target:self, selector:Selector("randomnumbers:"), userInfo: nil, repeats: true)
func randomnumbers(timer:NSTimer) {
var rockNamesArray:[String] = ["bird", "rock2", "rock3"]
var rockpos = Int(arc4random_uniform(UInt32(3)))
}
since the timer routine expects a timer object.
Edit You need to place it inside a (dummy) class like this:
class MyTimer {
var timer: NSTimer
init() {
timer = NSTimer(timeInterval:3, target:self , selector:Selector("randomnumbers:"), userInfo:nil, repeats:true)
}
func randomnumbers(timer:NSTimer) {
// ...
}
}
let myTimer = MyTimer()

Write selector: "randomnumbers" instead of selector: randomnumbers. You can instantiate the timer and start it at the same time using timer = NSTimer.scheduledTimerWithTimeInterval instead of timer = NSTimer(timeInterval.... Call this in your controller, e.g. in viewDidLoad
Please also note that your randomnumbers() does not do anything. You assign value to rockpos, but do not do anything with it. So you won't be able to see if the timer is working...

Related

Swift: how to order instructions in init method of a subclass

I need to implement this class:
class PinImageView: UIImageView {
var lastLocation:CGPoint
var panRecognizer:UIPanGestureRecognizer
init(imageIcon: UIImage?, location:CGPoint) {
self.lastLocation = location
super.init(image: imageIcon)
self.center = location
self.panRecognizer = UIPanGestureRecognizer(target:self, action:"detectPan:")
self.gestureRecognizers = [panRecognizer]
}
}
I think there is a kind of "cyclic" problem because the compiler wants me to initialize panRecognizer before calling super.init(image: imageIcon) but panRecognizer has self as target and we can use self only after calling the super init method.
How can I solve this?
This is a non-optional instance variable
var panRecognizer:UIPanGestureRecognizer
so you have to set a value for it before completing init, and specifically as you see, before calling super.
It doesn't quite need to be like that. Instead, it can be a lazy loading instance variable, so it's created the first time you request it.
Now, when you init you can setup the instance, call super, and then add the gesture recogniser (which will create the gesture in the process).
lazy var panRecognizer : UIPanGestureRecognizer = {
return UIPanGestureRecognizer(target:self, action:"detectPan:")
}()

ios8 Swift SpriteKit - Pause and Resume NSTimers in swift

I have searched many times on the internet but could not find the answer to this question. I know how to pause and resume NSTimers by using the invalidate functions - timer.invalidate. and I know how to resume them. But I have a SpriteKit game. When I pause my game, I stop everything and the timers. I know that I can stop them using .invalidate but when I invalidate them:
For example lets say I have a 5 second timer that runs continously that spawns one block.
When the timer reaches second 3 of the cycle and when I paused the game, and invalidate the timers. When I resume, Now the timers second goes back to 0 and I must wait another 5 seconds. I want it to continue from where it left off, 3 , and wait 2 seconds for the block to spawn.
blockGenerator.generationTimer?.invalidate()
self.isGamePaused = true
self.addChild(self.pauseText)
self.runAction(SKAction.runBlock(self.pauseGame))
e`
and when I resume it:
blockGenerator.generationTimer = ...
I have to wait another 5 seconds, I want the timer to continue from where it left off
If you can help me, I appreciate it thank you.
There is a way to pause/resume Timer instances, because using repeating timers we know the next fire date.
This is a simple class SRTimer and a protocol SRTimerDelegate
Protocol SRTimerDelegate
protocol SRTimerDelegate : AnyObject {
func timerWillStart(_ timer : SRTimer)
func timerDidFire(_ timer : SRTimer)
func timerDidPause(_ timer : SRTimer)
func timerWillResume(_ timer : SRTimer)
func timerDidStop(_ timer : SRTimer)
}
Class SRTimer
class SRTimer : NSObject {
var timer : Timer?
var interval : TimeInterval
var difference : TimeInterval = 0.0
var delegate : SRTimerDelegate?
init(interval: TimeInterval, delegate: SRTimerDelegate?)
{
self.interval = interval
self.delegate = delegate
}
#objc func start(_ aTimer : Timer?)
{
if aTimer != nil { fire(self) }
if timer == nil {
delegate?.timerWillStart(self)
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(fire), userInfo: nil, repeats: true)
}
}
func pause()
{
if timer != nil {
difference = timer!.fireDate.timeIntervalSince(Date())
timer!.invalidate()
timer = nil
delegate?.timerDidPause(self)
}
}
func resume()
{
if timer == nil {
delegate?.timerWillResume(self)
if difference == 0.0 {
start(nil)
} else {
Timer.scheduledTimer(timeInterval: difference, target: self, selector: #selector(start), userInfo: nil, repeats: false)
difference = 0.0
}
}
}
func stop()
{
if timer != nil {
difference = 0.0
timer!.invalidate()
timer = nil
delegate?.timerDidStop(self)
}
}
#objc func fire(_ sender : SRTimer)
{
delegate?.timerDidFire(self)
}
}
Make your class conform to the protocol SRTimerDelegate and initialize a SRTimer instance with
var timer : SRTimer!
timer = SRTimer(interval: 5.0, delegate: self)
Methods
start() calls the delegate method timerWillStart and starts the timer.
pause() saves the difference between the current date and the next fire date, invalidates the timer and calls the delegate method timerDidPause.
resume() calls the delegate method timerWillResume, creates a temporary one shot timer with the saved difference time interval. When this timer fires the main timer will be restarted.
stop() calls the delegate method timerDidStop and invalidates the timer.
When the timer fires, the delegate method timerDidFire is called.
First, let me say this - it is not possible to do with just NSTimer, there is no inbuilt function to do that (you can build logic around that as the answer from Vadian suggests). BUT.
Why NSTimer is not good idea
Lets stop and think for a little. For game objects and precise spawning, you should never use NSTimer in the first place. The problem is implementation of NSTimer (quoting the docs):
Because of the various input sources a typical run loop manages, the
effective resolution of the time interval for a timer is limited to on
the order of 50-100 milliseconds. If a timer’s firing time occurs
during a long callout or while the run loop is in a mode that is not
monitoring the timer, the timer does not fire until the next time the
run loop checks the timer. Therefore, the actual time at which the
timer fires potentially can be a significant period of time after the
scheduled firing time.
There are other problems with NSTimer but that is out of scope of that question.
Solution
What you can do instead, you should listen to delta time change in each update call
let delta = currentPreciseTime - previousPreciseTime
Now, when you have that delta, you can have your counter : Double, and on each update, you increase counter by delta.
let counter : Double
counter += delta
Now that your "timer" is running properly, you can check with simple condition if your period of time already passed, or do whatever you want with it:
let SPAWN_OBJECT_AFTER : Double = 5.0
if counter > SPAWN_OBJECT_AFTER {
// Do something on fire event
self.spawn()
// This line effectively restarts timer
counter -= SPAWN_OBJECT_AFTER
}
You can easily build your own, very easy timer class to do it. Also! This way you have control over what happens in your update call, which is where the update logic belongs. Timer breaks that model by allowing method execution outside that - it might be intended, but usually is not).
I built a games running in production every day and this is I'd say most common solution for periodic events, as it saves the most resources when used appropriately. Obviously not fitting for everything but definitely fits your need.
Hope it helps!
I don't believe there is a way to pause/resume a NSTimer in the way you are talking about. You must use timer.invalidate() and timer.fire(). However, perhaps you can use an int (that starts at 5 and goes down every second) to keep track of how many seconds the initial timer has before fires again and once the times fires again, make sure the new int value is passed to start the initial timer from the correct point in time.

Swift: Using NSTimer to call an action

I'm trying to figure how to call an action when an AVAudioPlayer hits specific second by using NSTimer.
Code:
var audioFile = try! AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("FileName", ofType: "mp3")!))
override func viewDidLoad() {
super.viewDidLoad()
NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("checkPlaybackTime:"), userInfo: nil, repeats: true)
}
func checkPlaybackTime(timer:NSTimer){
let seconds : NSTimeInterval = audioFile.currentTime
if (seconds == 20.0){
btnPausePlay.setTitle("Play", forState: UIControlState.Normal)
}
}
The action that I wanted to call is that the btnPausePlay button text sets to "Play" which it never does as you see as I've called it in the if statement.
Edit: It now works. I changed from if (seconds == 20.0 to if (seconds >= 20.0)
Although this answer does not make explicit use of NSTimer, it does leverage the method addPeriodicTimeObserverForInterval of the AVPlayer class to achieve the same purpose:
After initialising your player, do this:
let observerInterval = CMTimeMakeWithSeconds(0.5, 100) //this sets the interval at every half second
timeObserverToken = audioPlayer.addPeriodicTimeObserverForInterval(observerInterval, queue: nil, usingBlock: self.getPlaybackTimer)
Function getPlaybackTimer looks like this:
func getPlaybackTimer(time: CMTime){
let currentTime = audioPlayer.currentTime()
/* *** */
}
As nhgrif points out, your logic is flawed. You are comparing floating point values in a way that you should not. You need to take little variations into account, almost always when comparing floating point numbers.
In this case, however, this wouldn't suffice alone, as your comparison might take place every so little outside your checking interval. You are better of by using inequality here, i.e. >= 20.0. You might want to disable the timer then.

Synchronizing a for loop with timers in Swift

For part of the application that I'm working on involves a series of timers stored in an object array. Ideally, I would like to iterate through this array, get the duration for the timer and proceed to countdown. Once the timer has finished, go to the next item in the array and repeat the process
for(index, content) in enumerate timerList{
doneTimer = NSTimer.scheduledTimerWithTimeInterval(timerList[index].duration, target: self, selector: Selector("finished:"), userInfo: nil, repeats: false)
}
func finished(timer : NSTimer){
//do whatever and tell the loop to get the next item?
}
What is the best way to put this for loop "on hold" until the timer finishes before going to the next item in the array?
In any kind of GUI application, one of the basic rules is you never want to "pause" or "stop" or "wait." Instead, what you want to do is start an operation that will finish sometime later, go back to the GUI, and then when that operation finishes, pick up where you left off.
In the context of your question, it seems like what you're trying to do is find the nearest timer, start it up, display some stuff in the GUI, and then not do anything until the timer has fired.
With that in mind, the basic model you want to take is to find the earliest timer, start it up, display something in the GUI, and then go back to the system. When that timer fires, then you can repeat the process.
With a lot of vagueness in your original question, what you're going to wind up with is something like:
class TimerController {
var timers = [Timer]()
var runningTimer : NSTimer?
var countDownDisplay : UILabel?
class Timer {
let fireDate : NSDate
init(fireDate:NSDate) {
self.fireDate = fireDate
}
}
// given an array of NSTimer, find the earliest one to fire
func findEarliestTimer() -> Timer? {
return timers.sorted( { return $0.fireDate.timeIntervalSince1970 < $1.fireDate.timeIntervalSince1970 } ).first
}
func scheduleEarliestTimer() {
runningTimer?.invalidate()
runningTimer = nil
if let timer = findEarliestTimer() {
let duration = min(1.0, timer.fireDate.timeIntervalSinceNow)
runningTimer = NSTimer.scheduledTimerWithTimeInterval(duration, target: self, selector: "finish", userInfo: timer, repeats: false)
}
}
func finish(nstimer:NSTimer) {
let timer = nstimer.userInfo as Timer
// update the gui countdown
self.countDownDisplay?.text = "\(floor(timer.fireDate.timeIntervalSinceNow))"
// remove timer
timers = timers.filter { $0 !== timer }
// schedule the next timer
self.scheduleEarliestTimer()
}
}
Note that I'm using NSDate to hold when the timer goes off rather than duration, because "duration" only has significance when the start point is known. If you want a string of operations with a fixed duration to fire consecutively, modify as appropriate.
Since you're wanting to show some kind of countdown, you'll want to modify the NSTimer to fire every second until the appropriate time, each time it fires update the countdown.

What is the "cleartimeout" equivalent for Swift?

I'm trying to set up a timeout on a text input field that only implements the inner code a second after the user stops typing. So while the user is typing, I would continually call a cleartimeout and re-initiate the setTimeout.
I was originally looking at the performSelector function in Objective C, but it looks like there is no Swift equivalent for this.
Then I moved on to the GCD functions in Swift, looking for a way to execute this.
Here is what I came up with:
var delta: Int64 = 1 * Int64(NSEC_PER_SEC)
var time = dispatch_time(DISPATCH_TIME_NOW, delta)
dispatch_suspend(dispatch_get_main_queue())
dispatch_after(time, dispatch_get_main_queue(), {
println("timeout")
});
The dispatch_suspend function is not working as I was hoping.
Maybe the dispatch functions are not appropriate here?
You can use dispatch_after rather than one of the performSelector. But I don't think either of these is what you want.
If you are looking to call a block of code only after it's been idle for one second, then I think you want to use a timer (e.g. Timer is fine, or you could use a dispatch timer). Bottom line, every time you get keyboard interaction, see if there is a pending timer, and if so, invalidate it, and then schedule the new one.
So I might be inclined to do something like the following in Swift 3. For example, in iOS 10 and later, you can use the block rendition:
weak var timer: Timer?
func resetTimer() {
timer?.invalidate()
timer = .scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] timer in
// do whatever you want when idle after certain period of time
}
}
Or, if you need to support earlier iOS versions that do not have block-based timers:
weak var timer: Timer?
func resetTimer() {
timer?.invalidate()
timer = .scheduledTimer(timeInterval: 1, target: self, selector: #selector(handleIdleEvent(_:)), userInfo: nil, repeats: false)
}
#objc func handleIdleEvent(_ timer: Timer) {
// do whatever you want when idle after certain period of time
}
If you use this latter approach, though, recognize that this Timer keeps a strong reference to its target, so you might want to cancel the timer in viewDidDisappear (but not deinit). This is why we prefer the block-based rendition or GCD timers.
By the way, I am not sure what your intent of dispatch_suspend was, but don't suspend the main queue. You never want to do anything that could potentially interfere with the main queue's timely processing of events (i.e., never block/suspend the main queue).