How to stop infinite loop in swift during action? (iOS) - ios

I am trying to learn how to use "ON" and "OFF" buttons and the basics of swift in general. Everything is going well but when I try to create an infinite loop of vibration using the "ON" UIButton, the loop keeps reiterating and I can't press the "OFF" button to stop it.
I tried looking up ways to stop it but none of them mention how to apply the code. I am still new and learning how to use swift. I read about "UIViewAnimationOptionAllowUserInteraction" but I don't know how to put it into my code.
#IBOutlet weak var label: UILabel!
#IBAction func onSwitch(_ sender: UIButton) {
label.text = "ON"
vibrate()
}
#IBAction func offSwitch(_ sender: UIButton) {
label.text = "OFF"
vibrate()
}
func vibrate() {
while label.text == "ON" {
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
}
}

Your code is continuously re-running tasks in while loop. This is happening on a main thread, so your application is unable to catch off button tap, because every tick is consumed to re-run the AudioServicesPlayAlertSound().

The While Loop is wrong choice here !
You only vibrate is once by replacing the While With if condition to test your code correctly
OR
you can use Timer to vibrate every 3-5 Seconds
let timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.vibrate), userInfo: nil, repeats: true)
// must be internal or public.
#objc func vibrate() {
// Something cool
}

Related

IBAction button stays active

I have 2 buttons each below each other and depending on a function it shows what button is enabled and what button is disabled.
#IBOutlet weak var startBtn: workoutButton!
#IBOutlet weak var restBtn: workoutButton!
#IBAction func startBtnPressed(_ sender: AnyObject) {
startBtn.isHidden = true
startBtn.isEnabled = false
perform(#selector(workoutStartVC.revealRestModeBtn), with: 1, afterDelay: 10)
timeLeft = 0
myTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(workoutStartVC.timerRunning), userInfo: nil, repeats: true)
}
#IBAction func restBtnPressed(_ sender: AnyObject) {
print("rest mode button is pressed and i am showing a overlay right now with data count down")
}
When a click the restBtn it still executes the code in the startBtnPressed. How is this possible? Because when I click startBtnPressed 1 time it should disable the button and hide it. It hides it but I am still able to execute the function. So the timer goes twice as fast.
Thanks for the help!
Kevin.
Open your storyboard, select resetButton and make sure there is only one action attached in "Sent Events" section. Right now you will see both IBActions attached to it.
It should be like this:
You probably have something like this:
Delete the IBAction Connection and reconnect them.
This problem happens when both buttons are hooked to both methods.
This usually happens, if you have copied and pasted a button the storyboard.

problems with Timer on Swift

I'm having a problem with my timer on swift. When I press the play button once it works fine and adds one second every second but when I play it twice it adds 2 seconds every one second and so on. This is my code.
var timer = NSTimer()
var time = 0
var minute = 0
#IBOutlet var timerLabel: UILabel!
func increasedTimer()
{
time++
timerLabel.text = "\(minute)" + "m " + "\(time)" + "s"
if time == 59
{
minute++
time = -1
}
}
#IBAction func play(sender: AnyObject)
{
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("increasedTimer"), userInfo: nil, repeats: true)
}
#IBAction func reset(sender: AnyObject)
{
timer.invalidate()
time = 0
minute = 0
timerLabel.text = "0m 0s"
}
#IBAction func pause(sender: AnyObject)
{
timer.invalidate()
}
Every time you tap play you create and start an additional timer.
You have a few options to fix this.
From a user experience point of view, you need to enable/disable your three buttons (Play, Pause, Stop) appropriately. It makes no sense that the user can tap Play a 2nd time while the timer is going. And of course the Pause and Stop buttons shouldn't be enabled until Play has been tapped.
So start by fixing the user interface so the buttons make sense. Once that is done, you won't have the problem in your current code since the user won't be able to tap Play twice without first pausing or stopping.
In the short term, every time you play put another timer.invalidate() before starting a new timer. Definitely better to disable the play button.
button.setBackgroundImage(backgroundImage1, forState: .Normal)
button.setBackgroundImage(backgroundImage2, forState: .Highlighted)
button.setBackgroundImage(backgroundImage2, forState: .Disabled)
Examples of things you can do. You can supply different images for a greyed out play button, a selectable play button. Etc. Then change the forState. More options available then shown.
Or you can just change the text shading in the button with:
button.enabled = true
button.enabled = false

Unrecognised Selector... bug?

I edited some code based on what others have pointed out, but I keep getting the error stated above, saying I sent an "unrecognised selector". The selector for my timer, originally the error, has been amended, but Xcode is still complaining.
Here is my code:
import UIKit
class ViewController: UIViewController {
#IBOutlet var instructionsNew: UILabel!
#IBOutlet var lockStatusNew: UIImageView!
#IBOutlet var timerText: UILabel!
#IBAction func hackLockButton(sender: AnyObject){
var counter = 0
let timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateCounter", userInfo: nil, repeats: true)
func updateCounter() {
timerText.text = String(counter++)
}
while(timerText.text == "1") {instructionsNew.text = "loading"}
while(timerText.text == "2"){instructionsNew.text = "loading."}
while(timerText.text == "3") {instructionsNew.text = "loading.."}
while(timerText.text == "4"){instructionsNew.text = "loading..."}
while(timerText.text == "5") {instructionsNew.text = "hack successful!"
lockStatusNew.image = UIImage(named: "unlocked.png")
timer.invalidate()
}
}
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.
}
}
Please help me spot the problem. Thanks!
The issue is, you added the updateCounter inside the hackLockButton function.
You should place the method outside that function and it will work.
#IBAction func hackLockButton(sender: AnyObject)
{
// Code here
}
func updateCounter()
{
timerText.text = String(counter++)
}
Suggestion:
You don't want to write while(timerText.text == "1") {instructionsNew.text = "loading"} for showing that label. It can cause an infinite loop and hang your UI. Instead use a switch case like:
switch(counter)
{
case 1: instructionsNew.text = "loading"
// Write other cases too
}
I think you really, really need to get your head around how a timer works.
Your application has a run loop. When the user does anything, the run loop will call the appropriate code in your program, runs the code, and finish when that code is run. For example when you tap on a button, the run loop will call your button callback function, wait for it to finish, and then it can wait for the next thing to happen.
A scheduled timer inserts calls into that run loop. So every second the run loop calls updateCounter. updateCounter should do some stuff, and then return. It's not supposed to wait in a while loop at all. The while () inside it is badly, badly wrong.
You also do some other things upside down. You use your timerText label to control things. That's wrong. The label should display things. The updateCounter can update the counter, but then all other actions should depend on the value of the counter, not on the value of a user interface label! Imagine your boss tells you to display not 1, 2, 3, 4, 5 but one, two, three, four, five. You obviously change what goes into the label. But with your code you have to change code everywhere that reads the text of the label. Now imagine you don't want one, two, three, but the right text in the user's language...

How to invalidate an NSTimer that was started multiple times

I made a practice project in Swift to learn how NSTimer works. There is one button to start the timer and one button to invalidate it. It works fine when I tap each button once. However, when I tap the start timer button multiple times, I am no longer able to invalidate it.
Here is my code:
class ViewController: UIViewController {
var counter = 0
var timer = NSTimer()
#IBOutlet weak var label: UILabel!
#IBAction func startTimerButtonTapped(sender: UIButton) {
timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "update", userInfo: nil, repeats: true)
}
#IBAction func cancelTimerButtonTapped(sender: UIButton) {
timer.invalidate()
}
func update() {
++counter
label.text = "\(counter)"
}
}
I have seen these questions but I wasn't able to glean an answer to my question from them (many are old Obj-C pre-ARC days and others are different issues):
NSTimer() - timer.invalidate not working on a simple stopwatch?
Using an NSTimer in Swift
NSTimer doesn't stop
Unable to invalidate (Stop) NSTimer
NSTimer doesn't stop with invalidate
Can't invalidate, stop countdown NSTimer - Objective C
IOS: stop a NSTimer
You can add timer.invalidate() before starting a new timer in startTimerButtonTapped if you want to reset the timer each time the "start" button is tapped:
#IBAction func startTimerButtonTapped(sender: UIButton) {
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "update", userInfo: nil, repeats: true)
}
I was going to update with an explanation but #jcaron already did it in the comment, so I'm just quoting his text, no need to change it:
Every time you tap on the "Start Timer" button, you create a new timer, while leaving the previous one running, but with no reference to it (since you've overwritten timer with the new timer you just created). You need to invalidate the previous one before you create the new one.
I would like to suggest you to set timer to nil when press on cancel button.
And don't forget to set counter =0
When invalidating the Timer.

How to stop function in swift

So I'm doing little timer app in swift and I just have 2 buttons. One to start timer, and one to stop it and reset value to 0. I've figured out everything, and I have this function called timer which increases value for one each second for variable "Time". Problem is that when I click the STOP button, it resets the value to 0 but it keeps counting again.
Question is how do I stop that function from running.
Here is some code
var time = 0
func result() {
time++
print(time)
}
#IBAction func clickToStart(sender: AnyObject) {
result()
}
#IBAction func clickToStop(sender: AnyObject) {
time = 0
print(time)
}
override func viewDidLoad() {
super.viewDidLoad()
var timer = NSTimer()
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("result"), userInfo: nil, repeats: true)
}
Make your timer a member variable and call timer.invalidate() on it
Change your variable timer to be an instance variable. Make it weak, since the system owns it, and when you stop the timer it will be deallocated automatically.
In your clickToStop method, call timer.invalidate().
As others have pointed out using the timer.invalidate() functions, but you can also use flag variables in case you are doing something else not related to time or want another actions to stop.
Basically, you can just create a bool variable and when the user stops the actions, make the bool true. In the other function, make it that if bool is true then don't do the actions unless it's false. This works well for a mute button.
Like in my game, when it's a game over, I have a bool variable touchesInvalid that is at the very top of the touchesBegan function. If the touchesInvalid bool is true, then the user can't do any more actions that involved touch.

Resources