Swift 4 Timer.invalidate() returns "Expression resolves to an unused function" error - ios

I am making a game that has a timer displaying how long the current session has elapsed. I've followed a couple tutorials and read the documentation and can't seem to figure out why this is returning an error. Here is my code:
var timerT = Timer()
func startTimer () {
self.timerT = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timeFunc), userInfo: nil, repeats: true)
}
#objc func timeFunc (_ timer: Timer) {
timeS += 1
}
#IBAction func newGameAction(_ sender: UIButton) {
Timer.invalidate(timerT)
game = SetGame()
updateUIfromModel()
startTimer()
}
Much Mahalo.

You need to call invalidate() on an instance of a Timer, not on Timer itself.
Change:
Timer.invalidate(timerT)
to:
timerT.invalidate()

Related

Can't get timer to count down

I'm trying to learn how to use a timer in Swift, and every solution I look up is broken somehow or beyond my understanding.
I've tried with a closure, and without.
With a closure, I can actually get the app to run without crashing, but the timer just repeats 60, it doesn't count down and I don't know why.
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
var secondsRemaining = 60
print(secondsRemaining)
secondsRemaining -= 1
})
I've also tried using an #objc func with selector, but my app crashes right away with error Thread 1: "-[__SwiftValue countdown]: unrecognized selector sent to instance 0x600002970e40" (I haven't even gotten to trying the count down yet).
class ViewController: UIViewController {
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdown), userInfo: nil, repeats: true)
#objc func countdown() {
print("fire")
}
…
…
}
Any help is appreciated.
EDIT
If I place my variable outside the block, I get an error inside the block Instance member 'secondsRemaining' cannot be used on type 'ViewController'
class ViewController: UIViewController {
var secondsRemaining = 60
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
print(secondsRemaining)
secondsRemaining -= 1
})
}
But if I create a new project and put the timer inside viewDidLoad(), it works. I don't know why.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var secondsRemaining = 60
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
print(secondsRemaining)
secondsRemaining -= 1
})
}
}
Every time your timer fires, a new variable with an initial value of 60 is instantiated, thus always printing 60.
You have to declare your counting variable outside:
private var secondsRemaining = 60
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
print(self.secondsRemaining)
self.secondsRemaining -= 1
})
Or:
class ViewController: UIViewController {
private var secondsRemaining = 60
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdown), userInfo: nil, repeats: true)
#objc func countdown() {
print("fire")
self.secondsRemaining -= 1
}
}
You're declaring counter inside the timer call. That's why every time the timer executes it resets the counter to 60 seconds. You need to declare your timer outside the it's call:
var secondsRemaining = 60
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
print(secondsRemaining)
secondsRemaining -= 1
})
As mentioned in the comments above, don't reset the variable every single time you get in the closure and change you timer to the following. Create a simple obj function to do whatever logic you want. Also name your functionalists meaningfully
class XViewController: UIViewController {
private var secondsRemaining:Int = 60
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(startScrolling), userInfo: nil, repeats: true)
#objc func startScrolling() {
print(secondsRemaining)
secondsRemaining -= 1
}
}
Here is the fixed code:
class ViewController: UIViewController {
private var secondsRemaining = 60
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
print(secondsRemaining)
secondsRemaining -= 1
})
}
}

Can't invalidate timer swift

In my app I should use multiple timers but I don't want to add separate timers for every function, how can I create one function that simplifies creating multiple timers, I tried this code below, it works but I can't invalidate timers.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var first: UILabel!
#IBOutlet weak var second: UILabel!
var count = 0
var count2 = 0
var timer = Timer()
var timer2 = Timer()
override func viewDidLoad() {
super.viewDidLoad()
timerWithDifferentIntervals(myTimer: timer, interval: 1, target: self, selector: #selector(handle1), userInfo: nil, repeats: true)
timerWithDifferentIntervals(myTimer: timer2, interval: 1/6, target: self, selector: #selector(handle2), userInfo: nil, repeats: true)
}
func handle1() {
count += 1
first.text = "\(count)"
}
func handle2() {
count2 += 1
second.text = "\(count2)"
}
func timerWithDifferentIntervals(myTimer: Timer, interval: TimeInterval, target: Any, selector: Selector, userInfo: Any?, repeats: Bool) {
var timers = myTimer
timers = Timer.scheduledTimer(timeInterval: interval, target: target, selector: selector, userInfo: userInfo, repeats: repeats)
}
#IBAction func stop(_ sender: UIButton) {
timer.invalidate()
timer2.invalidate()
}
}
You never actually assign a new value to your variables. The timers you create are not saved anywhere, therefore you cannot invalidate them.
I would recommend the following changes:
var timer: Timer? {
didSet {
oldValue?.invalidate()
}
}
var timer2: Timer? {
didSet {
oldValue?.invalidate()
}
}
This will make sure the previous timer is always invalidated when assigning a new one. You can then invalidate using timer = nil or timer2 = nil.
Also, you should return the timer from your method:
func timerWithDifferentIntervals(interval: TimeInterval, target: Any, selector: Selector, userInfo: Any?, repeats: Bool) -> Timer {
return Timer.scheduledTimer(timeInterval: interval, target: target, selector: selector, userInfo: userInfo, repeats: repeats)
}
and use it in following way:
timer = timerWithDifferentIntervals(interval: 1, target: self, selector: #selector(handle1), userInfo: nil, repeats: true)
Although the method does basically nothing now, so there is no need for it
Add NStimers in an array, and each timer have a tag of its own.
Then whenever you want to invalidate a timer, reach it via the array and invalidate it.
Hope this helps!
I'm thinking you should use below code for timer create and invalidate
var timer : NSTimer?
func startTimer(_ timeInterval: Int, _ isRepeat: Bool)
{
if timer == nil {
timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: "timerFired", userInfo: nil, repeats: isRepeat)
}
}
func stopTimer()
{
if timer != nil {
timer!.invalidate()
timer = nil
}
}

Stopwatch App restarting from beginning when i press play button after pause button [duplicate]

This question already has answers here:
How can I pause and resume NSTimer.scheduledTimerWithTimeInterval in swift?
(9 answers)
Closed 6 years ago.
The below link is my code for StopWatch app. I have done everything is perfect. But when i press on PAUSE button timer is stopping, to restart again I am going to click START button. But at that time timer runs from starting not from where the pause button stops.
Please advice me to resolve this issue.
import UIKit
#IBAction func Play(sender: AnyObject) {
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("increment"), userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate()
}
#IBAction func Pause(sender: AnyObject) {
timer.invalidate()
}
#IBAction func Stop(sender: AnyObject) {
timer.invalidate()
time = 0
resultLabel.text = "\(0):\(0):\(0):\(0)"
}
To Start:
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("updateView"), userInfo: nil, repeats: true)
To Resume:
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("updateView"), userInfo: nil, repeats: true)
To Pause:
timer.invalidate
You should only be calling
startTime = NSDate.timeIntervalSinceReferenceDate()
When you start it originally. If you call that when you resume you will override the start time and it will be as if you are starting over.

Reference a variable value inside an #IBAction function from another class in Swift

I'm trying to make a laundry timer app in Swift where the washer and drying will have different starting times, counting down to 0.
func updateTime() {
...
var elapsedTime: NSTimeInterval = operationDuration - (currentTime - startTime)
...
}
In the var elapsedTime I have operationDuration (how long the washer or dryer will take) and later on in the #IBAction func for pressing the "washer" button I have
let operationDuration == 1800
However I am getting an error the the updateTime func that
'operationDuration' is not defined.
How can I do this? Thanks in advance.
edit:
Here is my washerButtonPress code:
#IBAction func washerButtonPress(sender: AnyObject) {
// TODO: start 30 minute countdown
if !timer.valid {
var operationDuration = 1800
let aSelector:Selector = "updateTime"
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate()
}
And how I'm trying to call it in my func updateTime()
var elapsedTime: NSTimeInterval = washerButtonPress.operationDuration - (currentTime - startTime)
and it returns '(AnyObject) -> ()' does not have a member named 'operationDuration'
I apologize for not knowing much but I'm pretty new to this
If this is merely a constant, you can declare it as a global variable.
let kOperationDuration = 1800
class Washer {
...
#IBAction func washerButtonPress(sender: AnyObject) {
if !timer.valid {
let aSelector:Selector = "updateTime"
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate()
}
}
...
}
Then in the Dryer class you can reference the constant.
class Dryer {
...
func updateTime() {
...
var elapsedTime: NSTimeInterval = kOperationDuration - (currentTime - startTime)
...
}
...
}

How can I use Timer (formerly NSTimer) in Swift?

I tried
var timer = NSTimer()
timer(timeInterval: 0.01, target: self, selector: update, userInfo: nil, repeats: false)
But, I got an error saying
'(timeInterval: $T1, target: ViewController, selector: () -> (), userInfo: NilType, repeats: Bool) -> $T6' is not identical to 'NSTimer'
This will work:
override func viewDidLoad() {
super.viewDidLoad()
// Swift block syntax (iOS 10+)
let timer = Timer(timeInterval: 0.4, repeats: true) { _ in print("Done!") }
// Swift >=3 selector syntax
let timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
// Swift 2.2 selector syntax
let timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: #selector(MyClass.update), userInfo: nil, repeats: true)
// Swift <2.2 selector syntax
let timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "update", userInfo: nil, repeats: true)
}
// must be internal or public.
#objc func update() {
// Something cool
}
For Swift 4, the method of which you want to get the selector must be exposed to Objective-C, thus #objc attribute must be added to the method declaration.
Repeated event
You can use a timer to do an action multiple times, as seen in the following example. The timer calls a method to update a label every half second.
Here is the code for that:
import UIKit
class ViewController: UIViewController {
var counter = 0
var timer = Timer()
#IBOutlet weak var label: UILabel!
// start timer
#IBAction func startTimerButtonTapped(sender: UIButton) {
timer.invalidate() // just in case this button is tapped multiple times
// start the timer
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}
// stop timer
#IBAction func cancelTimerButtonTapped(sender: UIButton) {
timer.invalidate()
}
// called every time interval from the timer
func timerAction() {
counter += 1
label.text = "\(counter)"
}
}
Delayed event
You can also use a timer to schedule a one time event for some time in the future. The main difference from the above example is that you use repeats: false instead of true.
timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false)
The above example calls a method named delayedAction two seconds after the timer is set. It is not repeated, but you can still call timer.invalidate() if you need to cancel the event before it ever happens.
Notes
If there is any chance of starting your timer instance multiple times, be sure that you invalidate the old timer instance first. Otherwise you lose the reference to the timer and you can't stop it anymore. (see this Q&A)
Don't use timers when they aren't needed. See the timers section of the Energy Efficiency Guide for iOS Apps.
Related
How to work with dates and time in Swift
Updated to Swift 4, leveraging userInfo:
class TimerSample {
var timer: Timer?
func startTimer() {
timer = Timer.scheduledTimer(timeInterval: 5.0,
target: self,
selector: #selector(eventWith(timer:)),
userInfo: [ "foo" : "bar" ],
repeats: true)
}
// Timer expects #objc selector
#objc func eventWith(timer: Timer!) {
let info = timer.userInfo as Any
print(info)
}
}
As of iOS 10 there is also a new block based Timer factory method which is cleaner than using the selector:
_ = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { timer in
label.isHidden = true
}
Swift 5
I personally prefer the Timer with the block closure:
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { (_) in
// TODO: - whatever you want
}
Swift 3, pre iOS 10
func schedule() {
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 20, target: self,
selector: #selector(self.timerDidFire(timer:)), userInfo: nil, repeats: false)
}
}
#objc private func timerDidFire(timer: Timer) {
print(timer)
}
Swift 3, iOS 10+
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(withTimeInterval: 20, repeats: false) { timer in
print(timer)
}
}
Notes
It needs to be on the main queue
Callback function can be public, private, ...
Callback function needs to be #objc
Check with:
Swift 2
var timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
Swift 3, 4, 5
var timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
You will need to use Timer instead of NSTimer in Swift 3.
Here is an example:
Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(YourController.update),
userInfo: nil,
repeats: true)
// #objc selector expected for Timer
#objc func update() {
// do what should happen when timer triggers an event
}
First declare your timer
var timer: Timer?
Then add line in viewDidLoad() or in any function you want to start the timer
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(action), userInfo: nil, repeats: false)
This is the func you will callback it to do something it must be #objc
#objc func action () {
print("done")
}
for swift 3 and Xcode 8.2
(nice to have blocks, but if You compile for iOS9 AND want userInfo):
...
self.timer = Timer(fireAt: fire,
interval: deltaT,
target: self,
selector: #selector(timerCallBack(timer:)),
userInfo: ["custom":"data"],
repeats: true)
RunLoop.main.add(self.timer!, forMode: RunLoopMode.commonModes)
self.timer!.fire()
}
func timerCallBack(timer: Timer!){
let info = timer.userInfo
print(info)
}
SimpleTimer (Swift 3.1)
Why?
This is a simple timer class in swift that enables you to:
Local scoped timer
Chainable
One liners
Use regular callbacks
Usage:
SimpleTimer(interval: 3,repeats: true){print("tick")}.start()//Ticks every 3 secs
Code:
class SimpleTimer {/*<--was named Timer, but since swift 3, NSTimer is now Timer*/
typealias Tick = ()->Void
var timer:Timer?
var interval:TimeInterval /*in seconds*/
var repeats:Bool
var tick:Tick
init( interval:TimeInterval, repeats:Bool = false, onTick:#escaping Tick){
self.interval = interval
self.repeats = repeats
self.tick = onTick
}
func start(){
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(update), userInfo: nil, repeats: true)//swift 3 upgrade
}
func stop(){
if(timer != nil){timer!.invalidate()}
}
/**
* This method must be in the public or scope
*/
#objc func update() {
tick()
}
}
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)
And Create Fun By The Name createEnemy
fund createEnemy ()
{
do anything ////
}
In Swift 3 something like this with #objc:
func startTimerForResendingCode() {
let timerIntervalForResendingCode = TimeInterval(60)
Timer.scheduledTimer(timeInterval: timerIntervalForResendingCode,
target: self,
selector: #selector(timerEndedUp),
userInfo: nil,
repeats: false)
}
#objc func timerEndedUp() {
output?.timerHasFinishedAndCodeMayBeResended()
}
If you init method of timer
let timer = Timer(timeInterval: 3, target: self, selector: #selector(update(_:)), userInfo: [key : value], repeats: false)
func update(_ timer : Timer) {
}
then add it to loop using method other selector will not be called
RunLoop.main.add(timer!, forMode: .defaultRunLoopMode)
NOTE : If you are want this to repeat make repeats true and keep the reference of timer otherwise update method will not be called.
If you are using this method.
Timer.scheduledTimer(timeInterval: seconds, target: self, selector: #selector(update(_:)), userInfo: nil, repeats: true)
keep a reference for later use if repeats is true.
I tried to do in a NSObject Class and this worked for me:
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
print("Bang!") }
NSTimer has been renamed to Timer in Swift 4.2.
this syntax will work in 4.2:
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(UIMenuController.update), userInfo: nil, repeats: true)

Resources