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)
}
Related
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?
I am trying get this code to produce multiple "flashes" to notify the user at the end of a countdown. Right now its working great for a single flash but I ideally would like three. Ive tried duplicating the code and putting the function in a loop to no avail.
Thanks in advance for the help!
//
Flash Function
func flash() {
if let wnd = self.view{
var v = UIView(frame: wnd.bounds)
v.backgroundColor = UIColor.black
v.alpha = 1
wnd.addSubview(v)
UIView.animate(withDuration: 1.618, animations: {
v.alpha = 0.0
}, completion: {(finished:Bool) in
print("inside")
v.removeFromSuperview()
})
}
}
//
Use of flash function
if (heatUpSeconds == 0) {
heatUpTimer.invalidate()
// testing screen flashing ideas here cameraView.frame
flash()
var timer:Timer!
timer = Timer.scheduledTimer(timeInterval: delayBetweenFlashes, target: self, selector: #selector(self.flash), userInfo: nil, repeats: true)
I might be heading totally wrong direction, but what I'm trying to achieve is to load some view first and invoke some methods that will be running afterwards infinitely, while at the same time giving the User the possibility to interact with this view.
My first guess was to use viewDidApper with UIView.animate, as per below;
class MainView: UIView {
...
func animateTheme() {
//want this to print out infinitely
print("Test")
}
}
class ViewController: UIViewController {
#IBOutlet weak var mainView: MainView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 1.0, delay: 0.0, options: .repeat, animations: {
self.mainView.animateTheme()
}, completion: nil)
}
}
The case is that I can only see a single "Test" printed out but this has to be invoked infinitely. Also, as I mentioned the User needs to have the possibility to interact with the view once everything is loaded.
Thanks a lot for any of your help.
ViewDidAppear will indeed be called latest when the view is loaded, but I don´t think the user can or will have the time to interact before it´s loaded.
If you want to load animations after everything is loaded you could create a timer and wait x seconds before you do that. Below example will be called 10 seconds after initiation.
var timer = Timer()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(animateSomething), userInfo: nil, repeats: false)
}
func animateSomething() {
// Animate here
}
Update
To make an infinite loop you can use the following snippet:
UIView.animate(withDuration: 5.0, delay: 0, options: [.repeat, .autoreverse], animations: {
self.view.frame = CGRect(x: 100, y: 200, width: 200, height: 200)
}, completion: nil)
I've done something very similar on one of my ViewControllers that loads a UIWebView. While the webpage is loading I show a UILabel with text "Loading" and every 1.5 seconds I add a period to the end of the text. I think the approach you describe above and mine shown below have the same effect and neither would be considered an infinite loop.
private func showLoadingMessage(){
let label = loadingLabel ?? UILabel(frame: CGRect.zero)
label.text = "Loading"
view.addSubview(label)
loadingLabel = label
addEllipsis()
}
private func addEllipsis() {
if webpageHasLoaded { return }
guard let labelText = loadingLabel?.text else {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
self.loadingLabel?.text = labelText + "."
self.addEllipsis()
}
}
I have a button (actually two nearly identical buttons) that unhide with an action in another UIView.
The buttons hide automatically 2 seconds after the panning in the second UIView ends using dispatch_after however I would like to keep the buttons visible if either is tapped while they are visible. Here is the timer property and two methods from the UIButton subclass. I have an #IBAction that calls "justTapped" in the ViewController.
var timer = NSTimer()
func hideAndShrink(){
if !self.hidden && !timer.valid{
self.hidden = true
}
}
func justTapped(){
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: "hideAndShrink", userInfo: nil, repeats: false)
}
Once either button is tapped, they will not hide.
Is the timer still valid while it calls the method in the selector?
The answer to this, thanks to luk2302, is Yes, the timer is valid when it sends the selector
How can I get around this?
As was suggested, I wrote a second method for the timer that hid the buttons without !timer.valid ?
It turned out that I did not need the second method at all as I just had any attempt to hide the buttons call justTapped instead. Here is the final code with expand and shrink animations for both buttons. self.direction is the direction I want the button to expand, from the bottom up, or the top down, and also determines the background image. I think it could have been an enumeration but I haven't figured those out yet.
func unhideAndExpand(){
if self.hidden{
expand(self.frame.size.height)
}
}
func hideAndShrink(){
if !self.hidden && !shrinking{
shrink(self.frame.size.height)
}
}
func justTapped(){
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(3.0, target: self, selector: "hideAndShrink", userInfo: nil, repeats: false)
}
func expand(height: CGFloat){
if self.direction == "up"{
self.transform = CGAffineTransformMake(1, 0, 0, 1/height, 1, height/2)
} else if self.direction == "down" {
self.transform = CGAffineTransformMake(1, 0, 0, 1/height, 1, -height/2)
}
self.hidden = false
UIView.animateWithDuration(0.5, delay: 0, options: .CurveEaseOut, animations:
{self.transform = CGAffineTransformIdentity}, completion: nil)
}
func shrink(height: CGFloat){
self.shrinking = true
UIView.animateWithDuration(0.5, delay: 0, options: .CurveEaseOut, animations:
{if self.direction == "up"{
self.transform = CGAffineTransformMake(1, 0, 0, 1/height, 1, height/2)
} else if self.direction == "down" {
self.transform = CGAffineTransformMake(1, 0, 0, 1/height, 1, -height/2)
}
}, completion: {_ in
self.transform = CGAffineTransformIdentity
self.hidden = true
self.shrinking = false})
}
Thanks everyone for the help.
Is the timer still valid while it calls the method in the selector?
Yes, it is
The following is a complete runnable playground example demonstrating that:
import Foundation
import XCPlayground
XCPSetExecutionShouldContinueIndefinitely()
class Bla {
var timer : NSTimer?
init() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "check", userInfo: nil, repeats: false)
NSTimer.scheduledTimerWithTimeInterval(1.1, target: self, selector: "checkAgain", userInfo: nil, repeats: false)
}
#objc func check(){
print(timer!.valid)
}
#objc func checkAgain(){
check()
}
}
let bla = Bla()
This will first output true and then output false. The timer is still valid when firing. As soon as you leave the fired method the timer gets invalidated.
That will render your if useless since the method will do nothing when fired by the timer because anything && !true evaluates to false.
What you can do to circumvent that issue is create a different method timerFired which sets some internal property of your class, e.g. var timerHasFired = false to true. Your `` then has to check that variable instead of the timer:
var timer = NSTimer()
var timerHasFired = false
func hideAndShrink(){
if !self.hidden && hideAndShrink {
self.hidden = true
}
}
func timerFired() {
timerHasFired = true
hideAndShrink()
}
func justTapped(){
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: "timerFired", userInfo: nil, repeats: false)
}
Are you sure you need !timer.valid check? If hideAndShrink is called only by timer it seems you can change it to
func hideAndShrink(){
self.hidden = true
}
hideAndShrink will be called only if timer is not invalidated so you can skip this check.
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()
})