NSTimer time interval not firing correctly - swift

I have an NSTimer that calls a function. The function flashes a different button each time it is called. i have the time interval set to one second and repeats is true, it stops repeating when the variable pcChoice == 10, but for some reason when i run the program it is delaying for a second and then calling the function a number of times right after each other. To the user, it looks like one second passes, then a number of buttons are flashed at once. What I need instead is for each button to flash one after another, with one second in between each flash.
override func viewDidLoad() {
var lit = [b0o,b1o,b2o,b3o,b4o,b5o,b6o,b7o,b8o]
var litIndex = 0
super.viewDidLoad()
for i in 1...10{
print(randomIndex)
print(computerChoices)
var buttonChoice = lit[randomIndex]
randomIndex = Int(arc4random_uniform(UInt32(lit.count)))
computerChoices[i] = randomIndex
print("yoyoyo")
self.view.setNeedsDisplay()
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("flashingButtons"), userInfo: nil, repeats: true)
}
}
func flashingButtons(){
var lit = [b0o,b1o,b2o,b3o,b4o,b5o,b6o,b7o,b8o]
one = computerChoices[pcChoice]
lit[one].setImage(UIImage(named: "redb.png"), forState: UIControlState.Normal)
pcChoice += 1
if pcChoice == 10{
timer.invalidate()
}
}

This line
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("flashingButtons"), userInfo: nil, repeats: true)
is executed 10 times because of your for loop. That gives you 10 different timers, each of which will invoke flashingButtons in about 1 second from now. Move that call out of the loop. You also don't need 10 calls to setNeedsDisplay in viewDidLoad.
Since you don't modify lit, make it a let instead of a var. I notice also that you have 9 elements in lit but you're looping through 10 times. The lit array in viewDidLoad is never used, and is different from the one in flashingButtons.
You could invoke arc4random_uniform inside flashingButtons each time the time fires. Or if you want the lights truly shuffled, each one being hit once, try something like this using GameplayKit:
let lit = [b0o,b1o,b2o,b3o,b4o,b5o,b6o,b7o,b8o]
let shuffledLit : [<lightobjects>] // whatever type of b0o, etc is
override func viewDidLoad() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("flashingButtons"), userInfo: nil, repeats: true)
shuffledLit = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(lit)
super.viewDidLoad()
self.view.setNeedsDisplay()
}

Related

Create a timer that pauses the code's flow Swift

Does a timer like let timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: false) actually stops the code for 10 seconds? Or will the code behind keeps running? I need a timer that could pause the code's flow for 10 seconds, and I don't want to use sleep or a closure! How can I implement this? Thanks.
You can probably use a Bool and the Timer in combination.
Take 2 Timers.
Timer1 - would at equal intervals call a method which internally checks the flag and would perform / not perform the action.
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
func fireTimer() {
if self.blockCode { return }
// your code here
}
Timer2 - would toggle the flag after a given set time, lets say 10 seconds.
self.blockCode = true
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
self?.blockCode = false
}
This would make you stop a code from execution for a fixed set of time without actually freezing the app like sleep would do.
Hope this helps.

automatic randomly change image view image from an array contain of image [swift]

I am trying to change and show image to Image View Randomly with few second delay from an array contain of images.
But When I press button action all code execute well what I want but I do not see real time change image automatically one by one in the simulator just show last random image
here is my code
#IBOutlet weak var imageViewTop: UIImageView!
let myArray = [#imageLiteral(resourceName: "A"), #imageLiteral(resourceName: "B"), #imageLiteral(resourceName: "C"), #imageLiteral(resourceName: "D")]
#IBAction func buttonAction(_ sender: UIButton) {
for i in 0..<myArray.count {
print("Process start \(i)")
imageViewTop.image = myArray.randomElement()
print(myArray.randomElement()!)
sleep(5)
print("Process End \(i)")
}
}
Here is my Console output
I see my Console output print statement delay with 5 second that's work fine. but not see changing image in my image-view into the simulator one by one
Process start 0
<UIImage:0x600000131050 named(main: B) {341, 341}>
Process End 0
Process start 1
<UIImage:0x600000131170 named(main: C) {341, 341}>
Process End 1
Process start 2
<UIImage:0x600000131050 named(main: A) {341, 341}>
Process End 2
Process start 3
<UIImage:0x600000131050 named(main: C) {341, 341}>
Process End 3
As per my understanding, you want sequential image change in your logic.
You can use Timer() to change your image after a specific time interval.
Check the below code for Image change with the help of Timer().
Step1:
//Create a Timer object.
var changeImageTimer: Timer?
Step2:
//Run your timer either from ViewDidLoad or on button click action.
#IBAction func buttonAction(_ sender: UIButton) {
//Check timer running or not
if let timer = changeImageTimer, timer.isValid {
changeImageTimer?.invalidate()
}
//Change Image Randomly
// changeImageTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(changeImageRandomly), userInfo: nil, repeats: true)
//Change Image in sequence
index = 0
changeImageTimer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(changeImageInSequence), userInfo: nil, repeats: true)
}
Step3:
//Timer function where you can put your image change logic this method calls after a time interval.
#objc func changeImageRandomly() {
//Rendom Pick from array
print("Image changes randomly.")
imageViewTop.image = myArray.randomElement()
}
#objc func changeImageInSequence() {
print("Image Change in Sequence with Index = ",index)
if index < myArray.count {
//Seq Pick from array
imageViewTop.image = myArray[index]
index += 1
} else {
changeImageTimer?.invalidate()
}
}
So on 3rd step, you have 2 options you can change the image randomly or in sequence by using index variable which will increment on every call and change the image.
Check this GIF:
https://imgur.com/a/OvN1US2
Hope this will help you to get an image update at a specific time interval.
instant of use for loop and sleep() I use Timer then it's work fine
#IBAction func buttonAction(_ sender: UIButton) {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
self.imageViewTop.image = self.myArray.randomElement()
// if [condition] {
// timer.invalidate() // Stop the repeats when condition match
// }
}
}

Swift scripted mouse movement unreliable inside scheduledTimer

I'm trying to write a piece of code that moves the mouse cursor on the screen to a specified location. Here are some ways I've tried to accomplish that:
Code below is used in all attempts (sandboxing disabled):
let mouseCursorPosition = CGPoint.zero;
CGAssociateMouseAndMouseCursorPosition(1)
Attempt 1:
CGWarpMouseCursorPosition(mouseCursorPosition)
Attempt 2:
CGDisplayMoveCursorToPoint(CGMainDisplayID(), CGPoint.zero)
Attempt 3:
let moveEvent = CGEvent(mouseEventSource: nil, mouseType: CGEventType.mouseMoved, mouseCursorPosition: mouseCursorPosition,
mouseButton: CGMouseButton.left)
moveEvent?.post(tap: CGEventTapLocation.cghidEventTap)
Expected behavior: update the mouse position along with the cursor on screen.
Every attempt so far works as expected if called directly inside viewDidLoad(). The cursor moves to the requested screen location as soon as the application loads. However, things get a bit weird when called inside a scheduledTimer. Using the code below:
class ViewController: NSViewController {
var mouseTimer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
mouseTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(moveCursor), userInfo: nil, repeats: true)
}
#objc func moveCursor() {
print(NSDate().timeIntervalSince1970)
let mouseCursorPosition = CGPoint.zero;
CGAssociateMouseAndMouseCursorPosition(1)
//insert attempt code here
}
}
The moveCursor method is called every 5 seconds (the timestamp appears every time), however the cursor does not move on the screen as long I don't move the physical mouse. However, when I move the mouse, the cursor first snaps to the last scripted position. What seems to happen is that the scripted location is registered, however it is not updated on screen until some sort of refresh event is triggered.
Any thoughts on the matter are greatly appreciated.
I cannot reproduce this problem on macOS 11.14. I created a new Cocoa app, using Storyboards, and edited ViewController.swift to the following:
import Cocoa
class ViewController: NSViewController {
var mouseTimer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
mouseTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(moveCursor), userInfo: nil, repeats: true)
}
#objc func moveCursor() {
print(NSDate().timeIntervalSince1970)
let mouseCursorPosition = CGPoint.zero;
CGAssociateMouseAndMouseCursorPosition(1)
CGWarpMouseCursorPosition(mouseCursorPosition)
}
}
I then ran it under Xcode. After 5 seconds (and every 5 seconds after) a timestamp is printed and the cursor jumps to the upper-left corner. I can move the cursor elsewhere and it jumps again.
I have the same behavior with or without calling CGAssociateMouseAndMouseCursorPosition. (I'm not sure why you're calling this in the test; 1 is the default value.)
Updated based on your comments and I still can't reproduce:
import Cocoa
class ViewController: NSViewController {
var mouseTimer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
mouseTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(moveCursor), userInfo: nil, repeats: true)
}
#objc func moveCursor() {
let mouseCursorPosition = CGPoint(x: Int.random(in: 0...500), y: Int.random(in: 0...500))
print("\(Date().timeIntervalSince1970): \(mouseCursorPosition)")
CGWarpMouseCursorPosition(mouseCursorPosition)
}
}
Every 5 seconds this causes the mouse pointer to jump to a new location, whether I move the mouse pointer or not. Is this identical to your code?

Changing the time interval of a Timer in swift spritekit

So, I have this timer that is setup to run a specific function (which are both shown below) on a time interval variable called 'frequency' when I try and change the timeinterval variable frequency to a lower number based on the score number it doesn't seem to change the rate at which it fires it just seems to fire at the same time even if the frequency is changed to a lower number
override func didMove(to view: SKView) {
Timer.scheduledTimer(timeInterval: frequency, target: self, selector: #selector(GameScene.spawnFallingOjects), userInfo: nil, repeats: true)
}
func spawnFallingOjects() {
if (GameState.current == .playing || GameState.current == .blackstone) {
guard usingThirdEye == false else { return }
let scoreLabel = childNode(withName: "scoreLabel") as! Score
let lane = [-100, -50 , 0, 50, 100]
let duration = 3.0
switch scoreLabel.number {
case 0...50:
frequency = 6.0
print("frequency has changed: \(frequency)")
case 51...100:
frequency = 4.5
print("frequency has changed: \(frequency)")
case 101...200000:
frequency = 1.1
print("frequency has changed: \(frequency)")
default:
return
}
let randomX = lane[Int(arc4random_uniform(UInt32(lane.count)))]
let object:Object = Object()
object.createFallingObject()
object.position = CGPoint(x: CGFloat(randomX), y: self.size.height)
object.zPosition = 20000
addChild(object)
let action = SKAction.moveTo(y: -450, duration: duration)
object.run(SKAction.repeatForever(action))
}
}
How do I make the timer fire faster when the frequency number changes to a lower number? should I recreate the timer at the end of the function?
You should actually avoid using Timer, Sprite kit has its own time functionality, and Timer does not work well with it and is a real pain to manage.
Instead, use SKAction's to wait and fire:
let spawnNode = SKNode()
override func didMove(to view: SKView) {
let wait = SKAction.wait(forDuration:frequency)
let spawn = SKAction.run(spawnFallingObjects)
spawnNode.run(SKAction.repeatForever(SKAction.sequence([wait,spawn])))
addChild(spawnNode)
}
Then to make it faster, just do:
switch scoreLabel.number {
case 0...50:
spawnNode.speed = 1
print("speed has changed: \(spawnNode.speed)")
case 51...100:
spawnNode.speed = 1.5
print("speed has changed: \(spawnNode.speed)")
case 101...200000:
spawnNode.speed = 2
print("speed has changed: \(spawnNode.speed)")
default:
return
}
The timeInterval property of Timer is a readonly property. (And your code is not trying to write a new frequency into the property...)
should I recreate the timer at the end of the function?
Nearly yes. Just you have no need to do it at the end.
With changing your method header like this:
func spawnFallingOjects(_ timer: Timer) {
You can access the fired Timer through the parameter timer, so you may need to write something like this just after switch scoreLabel.number {...}:
if frequency != timer.timeInterval {
//Invalidate old Timer...
timer.invalidate()
//And then allocate new one
Timer.scheduledTimer(timeInterval: frequency, target: self, selector: #selector(GameScene.spawnFallingOjects), userInfo: nil, repeats: true)
}
You can modify the fireDate property of an existing Timer (in case which still isValid), but recreating a Timer instance is not a heavy operation (comparing to creating an SKSpriteNode instance), so recreating a new Timer seems to be a little bit easier.

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.