Can't invalidate timer swift - ios

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

Related

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

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

Timer.scheduledTimer Swift 3 pre-iOS 10 compatibility

I need to schedule a Timer for firing a function every second but I see that in Xcode 8 beta 3 the scheduledTimer is only available for iOS 10.
Is there any alternative for using the timer in iOS 9 or previous versions?
Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { (timer) in print("Hi!")})
Solved using
Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(self.updateTime),
userInfo: nil,
repeats: true)
Run a timer with swift3,
var timer: Timer?
func startTimer() {
if timer == nil {
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(self.loop), userInfo: nil, repeats: true)
}
}
func stopTimer() {
if timer != nil {
timer?.invalidate()
timer = nil
}
}
func loop() {
let liveInfoUrl = URL(string: "http://192.168.1.66/api/cloud/app/liveInfo/7777")
let task = URLSession.shared.dataTask(with: liveInfoUrl! as URL) {data, response, error in
guard let data = data, error == nil else { return }
print(String(data: data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) ?? "aaaa")
}
task.resume()
}
Release the timer when you not use it.
Once scheduled on a run loop, the timer fires at the specified
interval until it is invalidated. A nonrepeating timer invalidates
itself immediately after it fires. However, for a repeating timer, you
must invalidate the timer object yourself by calling its invalidate()
method.
Here is sample code workable with compatibility:
if #available(iOS 10.0, *) {
Timer.scheduledTimer(withTimeInterval: 15.0, repeats: true){_ in
// Your code is here:
self.myMethod()
}
} else {
Timer.scheduledTimer(timeInterval: 15.0, target: self, selector: #selector(self.myMethod), userInfo: nil, repeats: true)
}
//Your method or function:
// MARK: - Method
#objc func myMethod() {
print("Hi, How are you.")
}
Swift 3
func runCode(in timeInterval:TimeInterval, _ code:#escaping ()->(Void))
{
DispatchQueue.main.asyncAfter(
deadline: .now() + timeInterval,
execute: code)
}
func runCode(at date:Date, _ code:#escaping ()->(Void))
{
let timeInterval = date.timeIntervalSinceNow
runCode(in: timeInterval, code)
}
func test()
{
runCode(at: Date(timeIntervalSinceNow:2))
{
print("Hello")
}
runCode(in: 3.0)
{
print("World)")
}
}
Updated for swift 3:
If you want to use Timer for some delay or any other purpose used below lines of code in your project;
// function defination:
func usedTimerForDelay() {
Timer.scheduledTimer(timeInterval: 0.3,
target: self,
selector: #selector(self.run(_:)),
userInfo: nil,
repeats: false)
}
func run(_ timer: AnyObject) {
print("Do your remaining stuff here...")
}
// function call:
self.usedTimerForDelay()
NOTE:- you can change the time interval as you want.
//Enjoy coding..!
Timer.scheduledTimer
Put it in the main thread.
You can use the following simple shim to provide the new block-based Timers to pre-iOS 10:
class TimerShim {
private var timer: Timer?
private let block: (Timer) -> Void
private init(timeInterval interval: TimeInterval, repeats: Bool, block: #escaping (Timer) -> Void) {
self.block = block
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(timerDidFire), userInfo: nil, repeats: repeats)
}
class func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: #escaping (Timer) -> Void) -> Timer {
return TimerShim(timeInterval: interval, repeats: repeats, block: block).timer!
}
#objc private func timerDidFire() {
block(timer!)
}
}
Usage example:
TimerShim.scheduledTimer(withTimeInterval: 5, repeats: false) { _ in
print("boom!")
}
The correct form is:
Timer.scheduledTimer(withTimeInterval: 2, repeats: false){_ in
"Here your code method"
}

How to make NSTimer for multiple different controllers? In swift

I make app on page-based.based. I have multiple different controllers. Every controller has NSTimer which turn on when the controller viewDidAppear(). I made that NSTimer turn off when controller is viewDidDisappear(). It works when I slowly slide between controllers but if I fast slide controller some timers don't turn off. I tried several different methods. The first method I post notification to the function which turn off timer but it also works when I slowly slide between controllers. The second method I created public class timer which I used for every controller it doesn't work for me. I don't know how can I make it. Please help me. I also have NSTimer in UIView which is located on UIViewController and I need that when UIViewController is disappear to turn off two timers. How can I make it?
My examples on ViewController
var timer: NSTimer!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
notificationCenter.postNotificationName("turnOnMemoryArc", object: nil)
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "showMemory", userInfo: nil, repeats: true)
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(true)
if timer != nil {
timer.invalidate()
}
notificationCenter.postNotificationName("turnOffMemoryArc", object: nil)
}
This code in the view
override func drawRect(rect: CGRect) {
notificationCenter.addObserver(self, selector: "turnOnMemoryTimer", name: "turnOnMemoryArc", object: nil)
notificationCenter.addObserver(self, selector: "turnOffMemoryTimer", name: "turnOffMemoryArc", object: nil)
// timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawMemoryArc", userInfo: nil, repeats: true)
}
func turnOnMemoryTimer() {
print("memory timer is on")
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawMemoryArc", userInfo: nil, repeats: true)
}
func turnOffMemoryTimer() {
if timer != nil {
timer.invalidate()
}
print("Memory timer turned")
}
The second method
import Foundation
public class GlobalTimer {
public init() { }
public var controllerTimer: NSTimer!
public var viewTimer: NSTimer!
}
ViewController
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
notificationCenter.postNotificationName("turnOnBatteryArc", object: nil)
if GlobalTimer().controllerTimer != nil {
GlobalTimer().controllerTimer.invalidate()
GlobalTimer().controllerTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "batteryStateForLabel", userInfo: nil, repeats: true)
} else {
GlobalTimer().controllerTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "batteryStateForLabel", userInfo: nil, repeats: true)
}
}
UIView
func turnOnBatteryTimer() {
print("turnOnBatteryTimer arc")
if GlobalTimer().viewTimer != nil {
GlobalTimer().viewTimer.invalidate()
GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
} else {
GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
}
}
The main problem in your code is GlobalTimer(). This creates new GlobalTimer instance every single time you do write GlobalTimer().
Four different instance in the following piece of code - no shared state.
if GlobalTimer().viewTimer != nil {
GlobalTimer().viewTimer.invalidate()
GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
} else {
GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
}
You can fix it with singleton pattern ...
public class GlobalTimer {
public static let sharedInstance = GlobalTimer()
private init() { }
public var controllerTimer: NSTimer!
public var viewTimer: NSTimer!
}
... by replacing all GlobalTimer() occurences ...
if GlobalTimer().viewTimer != nil {
... with GlobalTimer.sharedInstace ...
if GlobalTimer.sharedInstance.viewTimer != nil {
Next step is ! removal, which is dangerous and can lead to crash if you don't know what you're doing.
public class GlobalTimer {
public static let sharedInstance = GlobalTimer()
private init() { } <-- avoid instantiation outside the file
public var controllerTimer: NSTimer? <-- was !
public var viewTimer: NSTimer? <-- was !
}
Remaining step is to replace your code using GlobalTimer ...
if GlobalTimer().viewTimer != nil {
GlobalTimer().viewTimer.invalidate()
GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
} else {
GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
}
... with ...
GlobalTimer.sharedInstance.viewTimer?.invalidate()
GlobalTimer.sharedInstance.viewTimer = NSTimer.scheduled...
First line says - call invalidate() function on viewTimer if viewTimer != nil otherwise nothing happens (similar to nil messaging in ObjC, ...). This allows you to remove the if condition, because it invalidates timer or does nothing.
Second line just assigns new timer.
Try putting timer.invalidate() in the view will disappear instead of the did disappear. You shouldn't need the notifications at all when doing this.

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)

Trouble with NSTimer in Swift

I tried this but nothing happened. Is there something wrong with the selector?
func timer() {
var timer = NSTimer(timeInterval: 2.0, target: self, selector:Selector("function"), userInfo: nil, repeats: false)
}
func function() {
println("it worked")
}
You're just creating the timer, but not adding it to the run loop. You'll either need to use the equivalent scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: class method or schedule it on the run loop with addTimer:forMode:.
You can use this code for your NSTimer:
Swift 2
func timer() {
var timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: "timerFunc", userInfo: nil, repeats: false)
}
func timerFunc() {
println("it worked")
}
Swift 3, 4, 5
func timer() {
var timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(timerFunc), userInfo: nil, repeats: false)
}
#objc func timerFunc() {
print("it worked")
}
You have to use the scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: class method to run your timer.
Hope this can help you.
you create the Selector like below.
let #Selector : Selector = "timerFireMethod:"
and then create the timer
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0,
target: self,
selector: #Selector,
userInfo: userInfo,
repeats: true)

Resources