I am a complete beginner and am starting to learn the Swift programming language. I'm following a tutorial on Udemy and am having some problems with setting up a timer.
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var timer1 = Timer()
var counter = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
label.text = String(counter)
}
#IBAction func start(_ sender: Any) {
timer1 = Timer(timeInterval: 1, target: self, selector: #selector(tim), userInfo: nil, repeats: true)
}
#IBAction func pause(_ sender: Any) {
timer1.invalidate()
}
#IBAction func restart(_ sender: Any) {
timer1.invalidate()
counter = 0
label.text = String(counter)
}
#objc func tim() {
counter += 1
label.text = String(counter)
}
}
This is my code but the timer is not working. Please tell me where im going wrong.
You need to add timer to RunLoop
RunLoop.current.add(timer1, forMode: .commonModes)
Or use scheduledTimer
timer1 = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(tim), userInfo: nil, repeats: true)
Also in your restart function you need to do it again, because now you are just invalidating it and not starting again.
I am trying to make words in an array show up one at a time on a label, but with my code, the last word in the array shows up. What I want is for my label to display Hello [wait] (a certain amount of time preferably adjustable at some point) World [wait] Testing [wait] Array
Here's my code:
import UIKit
// Variables
let stringOfWords = "Hello World. Say Hello. Test Number One."
let stringOfWordsArray = stringOfWords.components(separatedBy: " ")
class ViewController: UIViewController {
// Outlets
#IBOutlet weak var labelWords: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
for word in stringOfWordsArray {
labelWords.text=(word)
}
}
}
I want to be able to have an adjuster for how fast the words show up and a start and stop button. If anyone can help me with that main part that would be great.
The simplest approach is to use Timer so you will be able to call start/stop it and adjust time (2 seconds in example below):
var timer: Timer?
var wordIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(update), userInfo: nil, repeats: true)
}
#objc func update() {
label.text = stringOfWordsArray[wordIndex]
if wordIndex < (stringOfWordsArray.count - 1) {
wordIndex += 1
}
else {
// start again ...
wordIndex = 0
}
}
#IBAction func startAction(_ sender: Any) {
timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(update), userInfo: nil, repeats: true)
}
#IBAction func stopAction(_ sender: Any) {
// remember to invalidate and nil it when you leave view
timer?.invalidate()
timer = nil;
}
I'm trying to make a simple timer in iOS using Swift.
The program is working fine but whenever my START UIButton is pressed the function of timer starts and runs multiple time as much as the button is pressed.
I want to disable the START UIButton as soon as the timer function starts so that it does not run multiple times.
Please help me for the same.
This is my code of ViewController
import UIKit
class ViewController: UIViewController {
var time = 0.0
var timer = Timer()
#IBOutlet weak var lbl: UILabel!
#IBAction func start(_ sender: UIButton)
{
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(ViewController.action), userInfo: nil, repeats: true)
}
#IBAction func pause(_ sender: UIButton)
{
timer.invalidate()
}
#IBAction func reset(_ sender: UIButton)
{
timer.invalidate()
time = 0.0
lbl.text = ("0")
}
func action()
{
time += 0.1
lbl.text = String(time)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
To disable your button you can just set it's isUserInteractionEnabled property in start function:
#IBOutlet weak var startButton: UIButton!
#IBAction func start(_ sender: UIButton) {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(ViewController.action), userInfo: nil, repeats: true)
startButton.isEnabled = false
}
Then set it back to true in pause and reset methods:
#IBAction func pause(_ sender: UIButton) {
timer.invalidate()
startButton.isEnabled = true
}
#IBAction func reset(_ sender: UIButton) {
timer.invalidate()
startButton.isEnabled = true
//the rest of your code
}
The most reliable way to ensure that the timer is only started and stopped once is writing two methods which check the current state (timer is not nil if it's currently running):
var timer : Timer?
func startTimer()
{
guard timer == nil else { return }
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(ViewController.action), userInfo: nil, repeats: true)
// disable start button
}
func stopTimer()
{
guard timer != nil else { return }
timer!.invalidate()
timer = nil
// enable start button
}
In the methods you can enable/disable the button(s) accordingly.
I know this is asked a lot but I've tried a lot of the other solutions on here and I can't seem to get it right.
So, I have a class that counts down, and at the end of the countdown a new view begins.
Here is the countdown class:
import Foundation
import UIKit
class CountdownController: UIViewController {
// MARK: Properties
#IBOutlet weak var countDownLabel: UILabel!
var count = 3
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
func update() {
if(count > 0) {
countDownLabel.text = String(count--)
}
else {
self.performSegueWithIdentifier("goestoMathTest", sender: self)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: Actions
}
and the error that comes up after the MathTestController shows is:
2016-05-26 23:43:48.579 TraderMathTestiOS[18654:951105] Warning: Attempt to
present <TraderMathTestiOS.MathTestController: 0x7fca824be7d0> on
<TraderMathTestiOS.CountdownController: 0x7fca824b9d70> whose view is not in
the window hierarchy!
**EDIT: So I tried another changing a few things and I think I narrowed down the issue. I changed the timer to be in viewDidLoad() and changed the repeats to 'false' and now MathTestController appears after 1 second with no warnings showing up. Here's the changed code:
override func viewDidLoad() {
super.viewDidLoad()
var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("update"), userInfo: nil, repeats: false)
}
func update() {
self.performSegueWithIdentifier("goestoMathTest", sender: self)
if(count > 0) {
countDownLabel.text = String(count--)
}
else {
self.performSegueWithIdentifier("goestoMathTest", sender: self)
}
}
I think the reason the error shows up is because the timer keeps repeating in the CountdownController even after MathTestController is called. Anybody know how to get my original functionality ( A timer that counts '3, 2, 1') without the error? Maybe I need to kill the timer somehow?
I fixed this finally. For anyone wondering, if your timer repeats you need to invalidate it if you're starting a new view. Here's the fixed code:
class CountdownController: UIViewController {
// MARK: Properties
#IBOutlet weak var countDownLabel: UILabel!
var timer = NSTimer()
var count = 3
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
func update() {
if(count > 0) {
countDownLabel.text = String(count--)
}
else {
timer.invalidate()
self.performSegueWithIdentifier("goestoMathTest", sender: self)
}
}
I've seen this a couple of times before, but it never occured to me what might be wrong.
Firstly, I want to create the effect of scrambling numbers like they do in those hacking scenes in movies. So, I made an NSTimer to make my delays such that every 0.2 seconds, the numbers change. Then, I made another timer to tell my first timer to
invalidate()
after two seconds. My code is as follows:
import UIKit
class MainPage: UIViewController {
#IBOutlet var genericDeviceName: UITextField!
#IBOutlet var hackButton: UIButton!
#IBOutlet var rightNumber: UILabel!
#IBOutlet var leftNumber: UILabel!
#IBOutlet var detectionText: UILabel!
#IBAction func deviceNameEnter(sender: AnyObject) {
detectionText.text = "Device detected: " + genericDeviceName.text!
if genericDeviceName.text == "" {
detectionText.text = "Error"
}
hackButton.alpha = 1
}
#IBAction func hackDevice(sender: AnyObject) {
var tries = 0
var timer = NSTimer()
var timerStop = NSTimer()
timer = NSTimer (timeInterval: 0.2, target: self, selector: "update", userInfo: nil, repeats: true)
timerStop = NSTimer (timeInterval: 2, target: self, selector: "endTimer", userInfo: nil, repeats: true)
let diceRoll = Int(arc4random_uniform(9) + 1)
let diceRollSecond = Int(arc4random_uniform(9) + 1)
UIView.animateWithDuration(0.25, animations:{
self.hackButton.transform = CGAffineTransformMakeRotation(CGFloat(M_PI))})
func update() {leftNumber.text = String(diceRoll)
rightNumber.text = String(diceRoll)
print("it worked!")}
func endTimer() {
timer.invalidate()
detectionText.text = "Access Granted!"
timerStop.invalidate()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blackColor()
}
So... what went wrong? The last few times I tried using NSTimers, they didn't work either. Is my concept of an NSTimer wrong? Or is there an error in my code? There was no error message triggered, it was just that the timer did not trigger and the numbers did not change. Not even "it worked!" was printed to the logs. Please help by suggesting some code. Thank you in advance!
UPDATE
I've updated my code. Here it is:
import UIKit
class MainPage: UIViewController {
#IBOutlet var genericDeviceName: UITextField!
#IBOutlet var hackButton: UIButton!
#IBOutlet var rightNumber: UILabel!
#IBOutlet var leftNumber: UILabel!
#IBOutlet var detectionText: UILabel!
#IBAction func deviceNameEnter(sender: AnyObject) {
detectionText.text = "Device detected: " + genericDeviceName.text!
if genericDeviceName.text == "" {detectionText.text = "Error"}
hackButton.alpha = 1
}
let diceRoll = Int(arc4random_uniform(9) + 1)
let diceRollSecond = Int(arc4random_uniform(9) + 1)
func update(timer: NSTimer) {leftNumber.text = String(diceRoll)
rightNumber.text = String(diceRoll)
print("it worked!")}
func endTimer(timerStop: NSTimer) {
timer.invalidate()
detectionText.text = "Access Granted!"
timerStop.invalidate()}
#IBAction func hackDevice(sender: AnyObject) {
var timer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: "update:", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
var timerStop = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: "endTimer:", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timerStop, forMode: NSRunLoopCommonModes)
UIView.animateWithDuration(0.25, animations:{
self.hackButton.transform = CGAffineTransformMakeRotation(CGFloat(M_PI))})
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blackColor()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Currently, it seems that the function "endTimer" does not work, due to the variable "timer" not being recognised. Please help. Thank you all so much for your time!
A few things: The selector for an NSTimer should end in a colon (e.g. "update:" or "endTimer:" And the function should take a single parameter: An NSTimer.
Second, the function that the timer calls must be a top-level function of the target. Your update method is a local function of your hackDevice, function, which won't work.
Third, you need to use scheduledTimerWithTimeInterval, as in ShahiM's answer:
var timer = NSTimer.scheduledTimerWithTimeInterval(
0.4,
target: self,
selector: "update:",
userInfo: nil,
repeats: true)
That code crashes if the function in your selector is a nested function because it's not visible to the timer.
Finally, it looks like you need to move the variables diceRoll and diceRollSecond out of your hackDevice function and make them instance variables of your class.
You should move your functions out of hackDevice. Nested functions like this are generally not used in Swift.
For example:
let diceRoll = Int(arc4random_uniform(9) + 1)
let diceRollSecond = Int(arc4random_uniform(9) + 1)
var timer = NSTimer()
#IBAction func hackDevice(sender: AnyObject) {
var tries = 0
var timer = NSTimer()
var timerStop = NSTimer()
timer = NSTimer (timeInterval: 0.2, target: self, selector: "update", userInfo: nil, repeats: true)
timerStop = NSTimer (timeInterval: 2, target: self, selector: "endTimer", userInfo: nil, repeats: true)
UIView.animateWithDuration(0.25, animations:{
self.hackButton.transform = CGAffineTransformMakeRotation(CGFloat(M_PI))})
}
func update() {
leftNumber.text = String(diceRoll)
rightNumber.text = String(diceRoll)
print("it worked!")
}
func endTimer() {
timer.invalidate()
detectionText.text = "Access Granted!"
timerStop.invalidate()
}
Try using this :
var timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "update", userInfo: nil, repeats: true)
Also move your update and endTimer methods outside the hackDevice method.
Explanation :
From Apple docs :
Use the timerWithTimeInterval:invocation:repeats: or timerWithTimeInterval: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 addTimer:forMode: method of the corresponding NSRunLoop object.)
.
Use the scheduledTimerWithTimeInterval:invocation:repeats: or scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer and schedule it on the current run loop in the default mode.
So in your code, you only create the timer but it does not start running. You have to either call the addTimer(_ timer: NSTimer,forMode mode: String) to start the timer or you can simply use scheduledTimerWithTimeInterval to launch the timer right away.
You don't nest this kind of function, selector will not find them because they will be exposed after the method exit, after the function leave the last } there will be no a update and endTiemr
Your timer should look like this let timer = NSTimer(timeInterval: 0.2, target: self, selector: Selector("update:"), userInfo: nil, repeats: true)
and on the other side func update(timer: NSTimer) {
Also try adding the timer to the run loop after initialisation:
let timer = NSTimer(timeInterval: 0.2, target: self, selector: Selector("update:"), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
In your update you redeclared the variable timer, this way you created a local variable which exists just in the method hackDevice: , remove the var before the timer = NSTimer.scheduledTimer...
Edit:
I rather edit this answer, because here i can add insert code snippet with proper newlines and indents:
class MainPage: UIViewController{
// Your IBOutlets
#IBOutlet var ...
var timer= NSTimter()
// Your methods
}