I have two views in my swift app. I am performing a segue as below.
ViewController.swift -----------------> GameViewController.swift
When loading the GameViewController an value array also passed to GameViewController.swift from ViewController.swift
A timer should be initialized in GameViewController.swift
I tried to initialize a timer and call a method through it, but it doesn't work.
Followings are my code snippets.
ViewController.swift
func signIn(difficultyLvl:String){
let username = usernameTxt.text
let password = passwordTxt.text
let url = URL(string: "http://192.168.1.106/speed/scoreBoardController.php?username="+username!+"&password="+password!+"&action=SIGNIN")
let task = URLSession.shared.dataTask(with: url!) {(data, response, error) in
let isPassed = String(data: data!, encoding:.utf8)?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
var gameViewControllerParams = [Int: [String: String]]()
gameViewControllerParams[0] = ["userId" : isPassed!]
gameViewControllerParams[1] = ["difficultyLvl" : difficultyLvl]
if(isPassed != "null"){
self.performSegue(withIdentifier: "gotoGame", sender: gameViewControllerParams)
}
}
task.resume()
}
GameViewController.swift
class GameViewController: UIViewController {
var gameViewControllerParams = [Int: [String: String]]()
override func viewDidLoad() {
super.viewDidLoad()
let _ = Timer.scheduledTimer(timeInterval: 1.0, target:self, selector: #selector(self.setCalculationLs), userInfo:nil,repeats: true)
}
func setCalculationLs(){
print("Timing")
}
}
Timers don't work on background queues (without some sleight of hand involving creating run loops or manually scheduling it on an existing run loop). But you should never initiate any UI update from anything other than the main queue, anyway.
So, since you're calling performSegue from a URLSession completion closure (which runs on a background queue), it's actually running viewDidLoad from the background queue, too. Thus the attempt to schedule the timer is failing. To get around this, you have to manually dispatch the performSegue code to the main queue:
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
...
if isPassed != "null" {
DispatchQueue.main.async {
self.performSegue(withIdentifier: "gotoGame", sender: ...)
}
}
}
If you're ever unsure whether some code is running on the main queue or not, refer to the documentation. Or you can use a dispatch precondition:
dispatchPrecondition(condition: .onQueue(.main))
That way it will (in debug builds) stop the app if you've accidentally invoked the code from a background queue.
Unrelated to your current problem, but as an aside, to avoid a strong reference cycle between the timer and the view controller, you generally want to keep a reference to the timer so that you can invalidate it when the view disappears (e.g. create timer in viewDidAppear and remove it in viewDidDisappear). Otherwise you can end up retaining the GameViewController after it was dismissed, e.g.:
class GameViewController: UIViewController {
weak var timer: Timer?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
timer = Timer.scheduledTimer(timeInterval: 1.0, target:self, selector: #selector(setCalculationLs(_:)), userInfo: nil, repeats: true)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
timer?.invalidate()
}
#objc func setCalculationLs(_ timer: Timer) {
print("Tick")
}
}
Or in iOS 10 or later, you can use the block-based variant with weak reference to self, and invalidate in deinit:
class GameViewController: UIViewController {
weak var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in
self?.setCalculationLs()
}
}
deinit {
timer?.invalidate()
}
func setCalculationLs() {
print("Tick")
}
}
Swift 5.5
I was having this issue as well while working in a segued view controller. Since segues are executed as a background thread to the main thread (main view controller), running timer within the segued view controller won't work. So, that being known and also knowing timers don't work in background threads, I did some digging and found this Apple Documentation gem.
I added the following line right after my scheduled timer. The timer now works beautifully. It is just sending the timer to the main thread for execution.
myTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.updateWithTimer), userInfo: nil, repeats: true)
RunLoop.main.add(myTimer, forMode: .common)
Related
I am trying to cancel a delayed execution of a function running on the main queue, in a tap gesture, I found a way to create a cancellable DispatchWorkItem, but the issue I have is that it's getting created every time while tapping, and then when I cancel the execution, I actually cancel the new delayed execution and not the first one.
Here is a simpler example with a Timer instead of a DispatchQueue.main.asyncAfter:
.onTapGesture {
isDeleting.toggle()
let timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { timer in
completeTask()
}
if !isDeleting {
timer.invalidate()
}
}
completeTask:
private func completeTask() {
tasksViewModel.deleteTask(task: task) // task is declared above this func at the top level of the struct and so is tasksViewModel, etc.
guard let userID = userViewModel.id?.uuidString else { return }
Task {
//do some async stuff
}
}
As you can see if I click it once the timer fires, but if I click it again, another timer fires and straight away invalidates, but the first timer is still running.
So I have to find a way to create only one instance of that timer.
I tried putting it in the top level of the struct and not inside the var body but the issue now is that I can't use completeTask() because it uses variables that are declared at the same scope.
Also, can't use a lazy initialization because it is an immutable struct.
My goal is to eventually let the user cancel a timed task and reactivate it at will on tapping a button/view. Also, the timed task should use variables that are declared at the top level of the struct.
First of all you need to create a strong reference of timer on local context like so:
var timer: Timer?
and then, set the timer value on onTapGesture closure:
.onTapGesture {
isDeleting.toggle()
self.timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { timer in
completeTask()
}
if !isDeleting {
timer.invalidate()
}
}
and after that you can invalidate this Timer whenever you need by accessing the local variable timer like this:
func doSomething() {
timer?.invalidate()
}
that is my solution mb can help you
var timer: Timer?
private func produceWorkItem(withDelay: Double = 3) {
scrollItem?.cancel()
timer?.invalidate()
scrollItem = DispatchWorkItem.init { [weak self] in
self?.timer = Timer.scheduledTimer(withTimeInterval: withDelay, repeats: false) { [weak self] _ in
self?.goToNextPage(animated: true, completion: { [weak self] _ in self?.produceWorkItem() })
guard let currentVC = self?.viewControllers?.first,
let index = self?.pages.firstIndex(of: currentVC) else {
return
}
self?.pageControl.currentPage = index
}
}
scrollItem?.perform()
}
for stop use scrollItem?.cancel()
for start call func
How can I run a function every minute?
In JavaScript I can do something like setInterval, does something similar exist in Swift?
Wanted output:
Hello World once a minute...
var helloWorldTimer = NSTimer.scheduledTimerWithTimeInterval(60.0, target: self, selector: Selector("sayHello"), userInfo: nil, repeats: true)
func sayHello()
{
NSLog("hello World")
}
Remember to import Foundation.
Swift 4:
var helloWorldTimer = Timer.scheduledTimer(timeInterval: 60.0, target: self, selector: #selector(ViewController.sayHello), userInfo: nil, repeats: true)
#objc func sayHello()
{
NSLog("hello World")
}
If targeting iOS version 10 and greater, you can use the block-based rendition of Timer, which simplifies the potential strong reference cycles, e.g.:
weak var timer: Timer?
func startTimer() {
timer?.invalidate() // just in case you had existing `Timer`, `invalidate` it before we lose our reference to it
timer = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] _ in
// do something here
}
}
func stopTimer() {
timer?.invalidate()
}
// if appropriate, make sure to stop your timer in `deinit`
deinit {
stopTimer()
}
While Timer is generally best, for the sake of completeness, I should note that you can also use dispatch timer, which is useful for scheduling timers on background threads. With dispatch timers, since they're block-based, it avoids some of the strong reference cycle challenges with the old target/selector pattern of Timer, as long as you use weak references.
So:
var timer: DispatchSourceTimer?
func startTimer() {
let queue = DispatchQueue(label: "com.domain.app.timer") // you can also use `DispatchQueue.main`, if you want
timer = DispatchSource.makeTimerSource(queue: queue)
timer!.schedule(deadline: .now(), repeating: .seconds(60))
timer!.setEventHandler { [weak self] in
// do whatever you want here
}
timer!.resume()
}
func stopTimer() {
timer = nil
}
For more information, see the the Creating a Timer section of Dispatch Source Examples in the Dispatch Sources section of the Concurrency Programming Guide.
For Swift 2, see previous revision of this answer.
If you can allow for some time drift here's a simple solution executing some code every minute:
private func executeRepeatedly() {
// put your code here
DispatchQueue.main.asyncAfter(deadline: .now() + 60.0) { [weak self] in
self?.executeRepeatedly()
}
}
Just run executeRepeatedly() once and it'll be executed every minute. The execution stops when the owning object (self) is released. You also can use a flag to indicate that the execution must stop.
Here's an update to the NSTimer answer, for Swift 3 (in which NSTimer was renamed to Timer) using a closure rather than a named function:
var timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
(_) in
print("Hello world")
}
You can use Timer (swift 3)
var timer = Timer.scheduledTimerWithTimeInterval(60, target: self, selector: Selector("function"), userInfo: nil, repeats: true)
In selector() you put in your function name
In swift 3.0 the GCD got refactored:
let timer : DispatchSourceTimer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)
timer.scheduleRepeating(deadline: .now(), interval: .seconds(60))
timer.setEventHandler
{
NSLog("Hello World")
}
timer.resume()
This is specially useful for when you need to dispatch on a particular Queue. Also, if you're planning on using this for user interface updating, I suggest looking into CADisplayLink as it's synchronized with the GPU refresh rate.
Here is another version algrid's answer with an easy way to stop it
#objc func executeRepeatedly() {
print("--Do something on repeat--")
perform(#selector(executeRepeatedly), with: nil, afterDelay: 60.0)
}
Here's an example of how to start it and stop it:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
executeRepeatedly() // start it
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NSObject.cancelPreviousPerformRequests(withTarget: self) // stop it
}
timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true, block: myMethod)
func myMethod(_:Timer) {
...
}
or
timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in
...
}
make sure to invalid the timer at some point like your time is no longer visible, or you object is deist
I want to call the method func adjustmentBestSongBpmHeartRate() every 1.1 second. I used Timer, but it doesn't work. I have read the document and found a lot of sample code, it still does work! Is there anything I missed?
timer = Timer.scheduledTimer(timeInterval: 1.1, target: self, selector: #selector(self.adjustmentBestSongBpmHeartRate), userInfo: nil, repeats: false)
timer.fire()
func adjustmentBestSongBpmHeartRate() {
print("frr")
}
I found that creating the timer in an OperationQueue Operation did not work. I assume this is because there is no runloop.
Therefore, the following code fixed my problem:
DispatchQueue.main.async {
// timer needs a runloop?
self.timeoutTimer = Timer.scheduledTimer(timeInterval: self.timeout, target: self, selector: #selector(self.onTimeout(_:)), userInfo: nil, repeats: false)
}
Timer methods with a selector are supposed to have one parameter: The timer itself. Thus your code should really look like this: 1
Timer.scheduledTimer(timeInterval: 1.1,
target: self,
selector: #selector(self.adjustmentBestSongBpmHeartRate(_:),
userInfo: nil,
repeats: false)
#objc func adjustmentBestSongBpmHeartRate(_ timer: Timer) {
print("frr")
}
Note that if your app only runs on iOS >= 10, you can use the new method that takes a block to invoke rather than a target/selector. Much cleaner and more type-safe:
class func scheduledTimer(withTimeInterval interval: TimeInterval,
repeats: Bool,
block: #escaping (Timer) -> Void) -> Timer
That code would look like this:
timer = Timer.scheduledTimer(withTimeInterval: 1.1,
repeats: false) {
timer in
//Put the code that be called by the timer here.
print("frr")
}
Note that if your timer block/closure needs access to instance variables from your class you have to take special care with self. Here's a good pattern for that sort of code:
timer = Timer.scheduledTimer(withTimeInterval: 1.1,
repeats: false) {
//"[weak self]" creates a "capture group" for timer
[weak self] timer in
//Add a guard statement to bail out of the timer code
//if the object has been freed.
guard let strongSelf = self else {
return
}
//Put the code that be called by the timer here.
print(strongSelf.someProperty)
strongSelf.someOtherProperty = someValue
}
Edit (updated 15 December)
1: I should add that the method you use in the selector has to use Objective-C dynamic dispatch. In Swift 4 and later, the individual methods you reference must be tagged with the #objc tag. In previous versions of Swift you could also declare the entire class that defines the selector with the #objc qualifier, or you could make the class that defined the selector a subclass of NSObject or any class that inherits from NSOBject. (It's quite common to define the method the timer calls inside a UIViewController, which is a subclass of NSObject, so it used to "just work".
Swift 3
In my case it worked after I added to my method the #obj prefix
Class TestClass {
private var timer: Timer?
func start() {
guard timer == nil else { return }
timer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(handleMyFunction), userInfo: nil, repeats: false)
}
func stop() {
guard timer != nil else { return }
timer?.invalidate()
timer = nil
}
#objc func handleMyFunction() {
// Code here
}
}
Try this -
if #available(iOS 10.0, *) {
self.timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false, block: { _ in
self.update()
})
} else {
self.timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(self.update), userInfo: nil, repeats: false)
}
Mostly the problem must have been because of iOS version of mobile.
Swift 5, Swift 4 Simple way only call with Dispatch Queue Async
DispatchQueue.main.async
{
self.andicator.stopAnimating()
self.bgv.isHidden = true
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false, block: { _ in
obj.showAlert(title: "Successfully!", message: "Video save successfully to Library directory.", viewController: self)
})
}
I have solved the question asked by myself.
I'm using apple watch to control my iphone app.
I try to press a button on apple watch to present a new viewcontroller on iphone.
When I write Timer in override func viewDidLoad(), Timer doesn't work. I move Timer to override func viewWillAppear() it works.
I think maybe there's something wrong with controlling by apple watch
I found that if you try to initialize the timer directly at the class-level, it won't work if you're targeting a selector in that same class. When it fires, it can't find the selector.
To get around this, I only initialize the timer after the object containing the selector has been initialized. If it's in the same class, put the initialization code in the ViewDidLoad or similar. Just not in the initializer. Then it will work. No dispatch queue needed.
Also, you do not need to use a selector that accepts the timer as a parameter. You can, but contrary to the answer with a ton of votes, that's not actually true, or more specifically, it works fine for me without it, just as you have it without it.
By the way, I think the reason the dispatch queue worked is because you're forcing the timer to be created after the object was initializing, confirming my above statement.
let timer:Timer?
override func viewDidLoad(){
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 1.1, target: self, selector: #selector(adjustmentBestSongBpmHeartRate), userInfo: nil, repeats: false)
timer.fire()
}
func adjustmentBestSongBpmHeartRate() {
print("frr")
}
Note: This is code typed from memory, not copied from Xcode so it may not compile, but hopefully you get the idea.
Swift3
var timer = Timer()
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.compruebaConexion), userInfo: nil, repeats: true)
my two cents.
I read about "didLoad" and when invoking it.
so we can use a delay:
class ViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
startTimer()
}
final func killTimer(){
self.timer?.invalidate()
self.timer = nil
}
final private func startTimer() {
// make it re-entrant:
// if timer is running, kill it and start from scratch
self.killTimer()
let fire = Date().addingTimeInterval(1)
let deltaT : TimeInterval = 1.0
self.timer = Timer(fire: fire, interval: deltaT, repeats: true, block: { (t: Timer) in
print("hello")
})
RunLoop.main.add(self.timer!, forMode: RunLoopMode.commonModes)
}
I am trying to nest a NSTimer.scheduledTimerWithTimeInterval call inside of an asynchronous NSURLSession.sharedSession().dataTaskWithRequest in Swift 2.0, but the code block inside of the test does not seem to get evaluated.
For example:
class testc{
#objc func test()
{
print("hello");
}
func loop()
{
if let url = NSURL(string : "https://www.google.com")
{
let url_req = NSURLRequest(URL: url);
let task = NSURLSession.sharedSession().dataTaskWithRequest(url_req)
{ data, response, error in
let timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(testc.test), userInfo: nil, repeats: true)
}
task.resume()
}
}
}
If we initialize this class and run loop nothing happens, the function test was never evaluated.
You need two changes to your code.
After creating the dataTask. You need to ask it to resume() to send the request.
The timer needs to be called on the main thread.
In your case, the dataTask is an asynchrnous task that runs on a background thread. In the implementation below, we are jumping back to the main thread to fire the timer.
I have added a counter to verify if the timer is firing repeatedly.
See updated code below.
class testc{
static var counter : Int = 0
#objc func test()
{ testc.counter++
print("hello -> \(testc.counter)");
}
func loop()
{
if let url = NSURL(string : "https://www.google.com")
{
let url_req = NSURLRequest(URL: url);
let task = NSURLSession.sharedSession().dataTaskWithRequest(url_req){ data, response, error in
dispatch_async(dispatch_get_main_queue(), {
self.setupTimer()
})
}.resume()
}
}
func setupTimer() {
let timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(testc.test), userInfo: nil, repeats: true)
}
}
let theBoy = testc()
theBoy.loop()
I have a problem with invalidating different timers.
I have multiple timers (NSTimer) on a viewcontroller(settingsVC):
class settingsVC: UIViewController {
// I use 12 timers
var timer1 = NSTimer()
// Seconds to end the timer. Set 12 timers
let timeInterval1:NSTimeInterval = 10
var timer2 = NSTimer()
let timeInterval2:NSTimeInterval = 20
var timer3 = NSTimer()
let timeInterval3:NSTimeInterval = 30
//and so on ... 12 timers
}
With a UIButton (Start) a segue is performed. And for every different value of the variable 'picked', a different timer will be started in the same class:
class settingsVC: UIViewcontroller {
let defaults = NSUserDefaults.standardUserDefaults()
let pickerDefaultsIntegerKey = "Picker" // nsuserdefaults key
#IBAction func start(sender: AnyObject) {
// segue to another viewcontroller
performSegueWithIdentifier("timerOn", sender: self)
if picked == 1 {
defaults.setInteger(1, forKey: pickerDefaultsIntegerKey)
timer1 = NSTimer.scheduledTimerWithTimeInterval(timeInterval1,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: false)
print("timer1 started")
} else if picked == 2 {
defaults.setInteger(2, forKey: pickerDefaultsIntegerKey)
timer2 = NSTimer.scheduledTimerWithTimeInterval(timeInterval2,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: false)
print("timer2 started")
} else if // ....and so on{........ }
}
The method fired if timer ends, see selector:
func timerDidEnd(timer:NSTimer){
print("timer ended")
// do other stuff
}
I invalidate the timers with a button (Reset) for values from a variable ('pickerSavedSelection') which is updated by saved values in NSUserdefaults:
#IBAction func reset(sender: AnyObject) {
if let pickerSavedSelection = defaults.integerForKey(pickerDefaultsIntegerKey) as Int?
{
if pickerSavedSelection == 1 {
timer1.invalidate()
} else if pickerSavedSelection == 2 {
timer2.invalidate()
} else if //...and so on{....}
}
All goes well, if I outcomment the perform segue line and just let the user stay on this viewcontroller.The timers get invalidated correctly then:
In the console I read 'timer1 started' and I do NOT read 'timer ended' when the resetButton is pressed.
But staying on this viewcontroller(settingsVC) is NOT the flow of my app.
When the perform segue line is executed and the user 'comes back' to the viewcontroller (settingsVC), the timers are not invalidated when user presses the resetButton:
In the console I read 'timer1 started' and I DO read 'timer ended' when the resetButton is pressed.
How should I stop the timers, when users will 'exit' the viewcontroller and come back to reset the timers?
Help is much appreciated! Thanks in advance
If I am not mistaken at any given point in time, you are only triggering one NSTimer. All your different timers are differentiated only in time intervals. So, my suggestion would be to keep only one NSTimer and have your time interval differentiated. With different value picked you should first invalidate the timer and then restart it with new time interval. That said, your reset will then be much simplified and you do not need to save pickerSavedSelection in NSUserDefaults. This is how I would re-write this code:
class settingsVC: UIViewController {
var timer = NSTimer()
#IBAction func start(sender: AnyObject) {
// segue to another viewcontroller
performSegueWithIdentifier("timerOn", sender: self)
if picked == 1 {
self.timer.invalidate()
self.timer = NSTimer.scheduledTimerWithTimeInterval(10,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: false)
print("timer1 started")
} else if picked == 2 {
self.timer.invalidate()
self.timer = NSTimer.scheduledTimerWithTimeInterval(20,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: false)
print("timer2 started")
} else if // ....and so on{........ }
}
#IBAction func reset(sender: AnyObject) {
self.timer.invalidate()
}
}
PS: As a side note, I would advise your NSTimer to start & stop from main thread. Use GCD for that.
It is because your selector is not called when your timer is invalidated, it is called everytime your timer is fired. Since the timer is non-repeat, the selector get called only once. When your press reset button, timer is actually invalidated, you just didn't know because you misunderstood scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: method.