Swift scripted mouse movement unreliable inside scheduledTimer - swift

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?

Related

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
// }
}
}

How to reference current SKScene from the AppDelegate in Swift

I have a SKScene that has a pause method. I want to be able to do something like this:
func applicationWillTerminate(_ application: UIApplication) {
pauseLevel()
}
However, I don't know how to get a reference to my SKScene from the AppDelegate.
I tried using
application.inputView
However, that is an UIView. How can I get an SKScene?
EDIT
deinit {
NotificationCenter.default.removeObserver(self)
}
override func didMove(to view: SKView) {
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.backgroundColor = UIColor(red:0.17, green:0.24, blue:0.31, alpha:1.0)
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
NotificationCenter.default.addObserver(self, selector: #selector(runPause), name: .UIApplicationWillResignActive, object: nil)
}
would that be sufficient and effective at removing the observer every time?
I would scrap your way of thinking. in iOS, notifications get sent for when application events happen. In your case, the notification is called UIApplicationWillTerminate
What you want to do is hook into this notification in your Scene class, I would recommend in the didMove(to:) method.
NotificationCenter.default.addObserver(self, selector: #selector(pauseLevel), name: .UIApplicationWillTerminate, object: nil)
Now when you do this, you need to remember to remove the observer
when you are removing the scene, so you want to use the code:
NotificationCenter.default.removeObserver(self)
At moments the scene is removed. I would recommend at the least putting it in the deinit
Now in Swift 4, things change a little bit. You need to add #objc to your pauseLevel function so that it can be exposed to objective c libraries.

Repeating iCarousel's scrolling

I'm using iCarousel in Swift 3 in Xcode. I'm trying to make it work like a sliding banner. I have this function:
func animate() {
DispatchQueue.global(qos: .userInteractive).async {
self.carousel.scrollToItem(at: 0, animated: false)
while(true){
sleep(3)
self.carousel.scroll(byNumberOfItems: 1, duration: 0.3)
}
}
}
I call it in viewDidLoad. It works but I think it's not as good as it could be. After I switch to another view, scrolling goes on and on. What would be the best approach to make it scroll only, when I'm seeing the view, that the carousel is in?
What solved the problem:
var timer: Timer!
self.timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(self.handleTimer), userInfo: nil, repeats: true)
func handleTimer(){
self.carousel.scroll(byNumberOfItems: 1, duration: 0.3)
}

NSTimer time interval not firing correctly

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()
}

swift: timer (every second) + gestureRecognizer crashs the GUI

I have a simple gestureRecognizer. When the gesture is "UP" then the menu slides up. "DOWN": The menu slides down. This works.
I have a timer too. It simply counts a number++ every second. This works, too. But together the timer is crashing my GUI.
The animation starts correctly, but when the timer calls my counter-method it's jumping back to the origin GUI.
What kind of problem is that? I found nothing at google, but I think I searched the wrong words. (English isn't my mothertongue and in german there isn't so much literature...)
Here are fragments of my code:
class ViewController: UIViewController, MKMapViewDelegate{
#IBOutlet weak var swipe: UILabel!
var number: Int = 0
#IBOutlet weak var menuView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
var topSwipe = UISwipeGestureRecognizer(target: self, action: Selector("handleSwipes:"))
var downSwipe = UISwipeGestureRecognizer(target: self, action: Selector("handleSwipes:"))
topSwipe.direction = .Up
downSwipe.direction = .Down
view.addGestureRecognizer(topSwipe)
view.addGestureRecognizer(downSwipe)
var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "counter", userInfo: nil, repeats: true)
}
func counter() {
number++
swipe.text = "\(number)"
}
func handleSwipes(sender:UISwipeGestureRecognizer) {
if (sender.direction == .Up) {
print("TOP")
var menuPosition = CGPointMake(self.menuView.frame.origin.x, self.menuView.frame.origin.y - 100.0)
UIView.animateWithDuration(0.65, animations: {
self.menuView.frame = CGRectMake(menuPosition.x,menuPosition.y,self.menuView.frame.size.width,self.menuView.frame.size.height)
})
}
if (sender.direction == .Down) {
menuChoose = true
UIView.animateWithDuration(0.65, animations: {
var menuPosition = CGPointMake(self.menuView.frame.origin.x, self.menuView.frame.origin.y + 100.0)
self.menuView.frame = CGRectMake(menuPosition.x,menuPosition.y,self.menuView.frame.size.width,self.menuView.frame.size.height)
})
}
}
}
I tried to lock the timer. So when the gesture begins, then just count the number without swipe.text = "(number)"
The animation goes to end, but after an "unlock" the GUI jumped back to the origin.
Thank u for some codesnippets or keywords that I can search :)
It seems that whenever a UILabel's text property is set, it redraws the view, resulting in resetting menuView's position (perhaps someone can elaborate on the "why" in a comment). If you use auto layout constraints to manipulate the position of menuView, it works. You can do this by setting spaceConstraint.constant += 100 or spaceConstraint.constant -= 100 and then calling
UIView.animateWithDuration(0.65, animations: {
self.view.layoutIfNeeded()
})