swift NSTimer userinfo - swift

I'm trying to pass a UIButton with a NSTimer's userinfo. I've read every post on stackoverflow on NSTimers. I'm getting very close but can't quite get there. This post has helped
Swift NSTimer retrieving userInfo as CGPoint
func timeToRun(ButonToEnable:UIButton) {
var tempButton = ButonToEnable
timer = NSTimer.scheduledTimerWithTimeInterval(4, target: self, selector: Selector("setRotateToFalse"), userInfo: ["theButton" :tempButton], repeats: false)
}
the function the timer runs
func setRotateToFalse() {
println( timer.userInfo )// just see whats happening
rotate = false
let userInfo = timer.userInfo as Dictionary<String, AnyObject>
var tempbutton:UIButton = (userInfo["theButton"] as UIButton)
tempbutton.enabled = true
timer.invalidate()
}

I realise you've managed to fix this but I thought I would give you a little more information about using NSTimer. The correct way to access the timer object and hence user info is to use it like below. When initialising the timer you can create it like this:
Swift 2.x
NSTimer.scheduledTimerWithTimeInterval(4, target: self, selector: Selector("setRotateToFalse:"), userInfo: ["theButton" :tempButton], repeats: false)
Swift 3.x<
Timer.scheduledTimer(timeInterval: 1, target: self, selector:#selector(ViewController.setRotateToFalse), userInfo: ["theButton" :tempButton], repeats: false)
Then the callback looks like this:
func setRotateToFalse(timer:NSTimer) {
rotate = false
let userInfo = timer.userInfo as Dictionary<String, AnyObject>
var tempbutton:UIButton = (userInfo["theButton"] as UIButton)
tempbutton.enabled = true
timer.invalidate()
}
Therefore you don't need to keep a reference to the timer and avoid often nasty global variables where possible. You may run into an issue in swift if your class doesn't inherit from NSObject where it says there is no callback defined but this can be easily fixed by adding #objc at the beginning of the function definition.

macOS 10.12+ and iOS 10.0+ introduces a block based API of Timer which is a more convenient way
func timeToRun(buttonToEnable: UIButton) {
timer = Timer.scheduledTimer(withTimeInterval:4, repeats: false) { timer in
buttonToEnable.enabled = true
}
}
A one shot timer will be invalidated automatically after it fires.
An similar convenient way for a one shot timer is using GCD (DispatchQueue.main.asyncAfter)
func timeToRun(buttonToEnable: UIButton) {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4)) {
buttonToEnable.enabled = true
}
}

I was just going to post this as I read over it before I posted. I noticed that I had timer.invalidate() before userinfo so that's why it wasn't working. I will post it as it may help somebody else.
func setRotateToFalse(timer:NSTimer) {
rotate = false
timer.invalidate()
let userInfo = timer.userInfo as Dictionary<String, AnyObject>
var tempbutton:UIButton = (userInfo["theButton"] as UIButton)
tempbutton.enabled = true
}

Related

Capture Application inactivity for certain time

I am trying to find out if user does not click or scroll or any other type of events in the application for 3 min; or in other words user is not interactive in the application, then the application will sign him out. similar bank application.
I could able to add GestureRecognizer as follows, but I want to catch up all the activities on the UI, is there a way to handle anything like that?
Set up a tap recognizer on collectionView:
In the viewDidLoad add the following:
let collectionViewTap = UITapGestureRecognizer(target: self, action: #selector(collectionViewTap))
collectionView.addGestureRecognizer(collectionViewTap)
Declare this function which will be called when the collectionView is tapped:
func collectionViewTap() {
print("collectionViewTap")
}
I could able to implement the time difference as follows to see timeDifference as well.
func isUserRequiredToLogin(){
let lastActivityDateAndTime = UserDefaults.standard.object(forKey: "lastActivityDateAndTime") as! Date
let currentDate = Date()
let minutes = currentDate.minutes(from: lastActivityDateAndTime)
print(minutes)
}
var timer: Timer?
func resetTimer() {
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(timeIsOur), userInfo: nil, repeats: false)
}
#objc func timeIsOur() {
//logout
//log something
//alerts
}
you can add a Timer to your code and reset it in any event you want. If timer won't be reset - than timeIsOur will be executed and you do whatever you want - (logout or log something)
try to use this https://www.zerotoappstore.com/how-to-detect-user-inactivity-swift.html It works in my production app.

Timer isn’t performed in the case of sub-task

I’m trying to implement a function using timer and have found timer is not performed in case that it is called through callback function of “URLSession.dataTask”.
In below case, “callee” function is called.
class TimerClass {
func caller() {
timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: #selector(callee),
userInfo: nil,
repeats: false)
}
func callee() {
print(“OK”)
}
}
class AnyClass {
func any() {
let timer:TimerClass=TimerClass()
timer.caller()
}
}
But below “callee” is not called. (I’ve confirmed “caller” function is performed)
class TimerClass {
func caller() {
timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: #selector(callee),
userInfo: nil,
repeats: false)
}
func callee() {
print(“OK”)
}
}
class AnyClass {
func any() {
func cb(data:Data?, response:URLResponse?, err:Error?) {
let timer:TimerClass=TimerClass()
timer.caller()
}
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: cb)
.
.
.
}
}
I think maybe because it was performed by sub-task.
Can anyone let me know how do I correct the code?
Check the reference of the Timer class:
Use the scheduledTimer(timeInterval:invocation:repeats:) or scheduledTimer(timeInterval:target:selector:userInfo:repeats:) class
method to create the timer and schedule it on the current run loop in
the default mode.
Use the init(timeInterval:invocation:repeats:) or init(timeInterval:target:selector:userInfo:repeats:) class method to
create the timer object without scheduling it on a run loop. (After
creating it, you must add the timer to a run loop manually by calling
the add(_:forMode:) method of the corresponding RunLoop object.)
So, if you want to schedule the timer in the main RunLoop, you can write something like this:
DispatchQueue.main.async {
Timer.scheduledTimer(
timeInterval: 0.1,
target: self,
selector: #selector(self.callee),
userInfo: nil,
repeats: false
)
}
Not using the Timer class, but this seems to be better:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.callee()
}
(UPDATED) It is clearly stated that the RunLoop class is generally not considered to be thread-safe. You should not use the old code (hidden in the edit history), even if it seemingly works in some limited conditions.

Cannot create Struct with CGRect based on super.view.frame [duplicate]

Just a simple task, but I'm in trouble. Trying to make a different way but it fails.
How to init NSTimer with declared previously variable? Neither var nor let helps.
The initial value of a property (in your case: timer) cannot depend on another property of the class (in your case: interval).
Therefore you have to move the assigment timer = NSTimer(interval, ...) into a method of the
class, e.g. into viewDidLoad. As a consequence, timer has to be defined as an
optional or implicitly unwrapped optional.
Note also that Selector(...) takes a literal string as argument, not the method itself.
So this should work:
class ViewController: UIViewController {
var interval : NSTimeInterval = 1.0
var timer : NSTimer!
func timerRedraw() {
}
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer(timeInterval: interval, target: self, selector: Selector("timerRedraw"), userInfo: nil, repeats: true)
// ...
}
// Other methods ...
}
Try:
var interval:NSTimeInterval = 1.0
var timer = NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "timerRedraw:", userInfo: nil, repeats: true)
pro-tip and hopefully an appreciated FYI: Swift functions should also start with lower case letters (i.e. "timerRedraw").

Swift dictionary with String key and UIImageView as value [duplicate]

Just a simple task, but I'm in trouble. Trying to make a different way but it fails.
How to init NSTimer with declared previously variable? Neither var nor let helps.
The initial value of a property (in your case: timer) cannot depend on another property of the class (in your case: interval).
Therefore you have to move the assigment timer = NSTimer(interval, ...) into a method of the
class, e.g. into viewDidLoad. As a consequence, timer has to be defined as an
optional or implicitly unwrapped optional.
Note also that Selector(...) takes a literal string as argument, not the method itself.
So this should work:
class ViewController: UIViewController {
var interval : NSTimeInterval = 1.0
var timer : NSTimer!
func timerRedraw() {
}
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer(timeInterval: interval, target: self, selector: Selector("timerRedraw"), userInfo: nil, repeats: true)
// ...
}
// Other methods ...
}
Try:
var interval:NSTimeInterval = 1.0
var timer = NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "timerRedraw:", userInfo: nil, repeats: true)
pro-tip and hopefully an appreciated FYI: Swift functions should also start with lower case letters (i.e. "timerRedraw").

NSTimer.scheduledTimerWithTimeInterval in Swift Playground

All the examples I've seen on using the "NSTimer.scheduledTimerWithTimeInterval" within Swift show using the "target: self" parameter, but unfortunately this doesn't work in Swift Playgrounds directly.
Playground execution failed: <EXPR>:42:13: error: use of unresolved
identifier 'self'
target: self,
Here's an example referenced above that results in the error:
func printFrom1To1000() {
for counter in 0...1000 {
var a = counter
}
}
var timer = NSTimer.scheduledTimerWithTimeInterval(0,
target: self,
selector: Selector("printFrom1To1000"),
userInfo: nil,
repeats: false
)
timer.fire()
You really should not be using NSTimer these days. It's consumes a lot of resources, causes unnecessary battery drain, and the API lends itself to ugly code.
Use dispatch_after() instead:
dispatch_after(0, dispatch_get_main_queue()) { () -> Void in
for counter in 0...1000 {
var b = counter
}
}
Of course, since the timer will fire after playground does it's stuff you will need an equivalent of timer.fire() to force the code to execute immediately instead of after a 0 second delay. Here's how that works:
let printFrom1To1000 = { () -> Void in
for counter in 0...1000 {
var b = counter
}
}
dispatch_after(0, dispatch_get_main_queue(), printFrom1To1000)
printFrom1To1000()
To get this to run directly within a Swift Playground, you need to embed the printFrom1To1000 function within a class and then set an instance of that class to the "target:" parameter instead of using "self".
Here's a full working example:
class myClass: NSTimer{
func printFrom1To1000() {
for counter in 0...1000 {
var b = counter
}
}
}
let myClassInstance = myClass()
var timer = NSTimer.scheduledTimerWithTimeInterval(0,
target: myClassInstance,
selector: Selector("printFrom1To1000"),
userInfo: nil,
repeats: false
)
timer.fire()
If you already have an object you are referencing (i.e., updating a label), you can extend that type and use that function as the Selector. I find this easier than creating a whole new class and instantiating a new object from it.
extension SKLabelNode {
func updateMe() {
count++
label.text = "\(count)"
}
}
var timer = NSTimer.scheduledTimerWithTimeInterval(0.25,
target: label,
selector: Selector("updateMe"),
userInfo: nil,
repeats: true)
timer.fire()