Continuously check for response in Swift - ios

I have a boolean variable called flag with initial value of false. Based on a successful process, it's set to true. There is a button alert, when tap it, it checks for flag's value along with a spinning image on UI, if flag is true, then a success message should displayed. otherwise, it should keep continuing response check (ten times for 5 seconds).
This is my functionality. I've been using NStimer to achieve this. Here is the code snippet:
var timer = NSTimer()
var count = 10
var flag: Bool = false
#IBOutlet weak var alert: UIButton!
#IBAction func alertAction(sender: AnyObject) {
timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: #selector(ViewController.prints), userInfo: nil, repeats: true)
}
func prints(){
if(count > 0)
{
if flag == false{
**Spinning Image**
count -= 1
} else {
count = 0
}
} else {
timer.invalidate()
}
}
The spinning image stops and continues after every 5 seconds ( in case response takes more than 5 seconds). I wish to spin the image continuously without a break. Can someone please help?
Thanks in advance!

Polling is the most desperate asynchronous pattern and almost always wrong. Learn a bit about value observation and reactive pattern.
var flag = false {
didSet {
if flag {
// stop spinning
}
}
}
func alertAction() {
// start spinning
}

From what I understand, this code will do what you intend. If you are using a UIActivityIndicator. Ensure to start it where I started the rotationAnimation and stop it when invalidating your timer.
Swift 3 Example
#IBOutlet weak var pin: UIImageView!
var timer: Timer?
var count: Int = 5
var flag: Bool {
return count == 0
}
#IBAction func buttonPressed(_ sender: AnyObject) {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.fromValue = 0
rotationAnimation.toValue = 2 * M_PI
rotationAnimation.duration = 0.6
rotationAnimation.isCumulative = true
rotationAnimation.repeatCount = Float.infinity
pin.layer.add(rotationAnimation, forKey: "rotate")
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(prints), userInfo: nil, repeats: true)
}
func prints() {
if flag {
pin.layer.removeAllAnimations()
timer?.invalidate()
} else {
count = count - 1
}
}

Related

Updating a timer in ios when app goes in and out of background

I have an app that has a timer in a view controller that counts down from 5 minutes to 0:00.
It stops when the app goes into the background per ios/Apple rules of app suspension.
How do I grab the time before it goes to sleep and update it in seconds when it comes back? I know you can't use background processing for long so avoiding that.
Seems like a good use case for UserDefaults.
This can be done in your main view controller.
ViewController.swift
let defaults = UserDefaults.standard
var timer = Timer()
var timerCount = 300
override func viewDidLoad() {
super.viewDidLoad()
let timeLeft = defaults.integer(forKey: "timerLeft1")
if timeLeft != 0 {
timerCount = timeLeft
}
startTimer()
}
func startTimer() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateCounter), userInfo: nil, repeats: true)
}
#objc func updateCounter() {
if timerCount > 0 {
timerCount -= 1
defaults.set(timerCount, forKey: "timerLeft1")
} else {
timerCount = 300
}
}
Apple docs: UserDefaults

How do I cancel a delayed action if the same button that triggered it is clicked before the delayed time is executed?

class viewcontroller1: UIViewController {
I have these two images. When the next button is clicked, image1 changes to image2 after 10 seconds. However, I have no idea how to cancel/reset the delayed action if the next button is clicked again before the 10 seconds has expired. This is the code I have so far...
var image1: UIImageView!
var image2: UIImageView!
func delay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
#IBAction func nextbutton(_ sender: Any) {
image1.image = UIImage(named: "image1")
delay(10) { self.image2.image = UIImage(named: "image2")
}
}
}
I would really appreciate any help. Thanks
anyncAfter can not be canceled
As per comment you have to use Timer following is example
Create global variable
var timer:Timer?
and On action
#IBAction func btnImageChangedTapped(_ sender: UIButton) {
if (self.timer != nil) {
self.timer?.invalidate()
self.timer = nil;
}
timer = Timer.scheduledTimer(timeInterval: yourTime, target: self, selector: #selector(changeImage:), userInfo: sender, repeats: false)
}
u can try this one
var timer : Timer?
func startTimer(){
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(self.YourImageChangeAction), userInfo: nil, repeats: false)
}
func resetTimer(){
timer?.invalidate()
startTimer()
}
add func resetTimer to your UiButton sender.
You could use a click counter in your VC and test it upon execution of the delayed code.
for example:
var clickCount = 0
#IBAction func nextbutton(_ sender: Any)
{
clickCount += 1
let wantsClickCount = clickCount
image1.image = UIImage(named: "image1")
delay(10)
{
guard self.clickCount == wantsClickCount
else { return }
self.image2.image = UIImage(named: "image2")
}
}
if the button was clicked again before the closure executes, the clickCount will no longer match and the timed code will do nothing.
This would even work with very fast clicking. Note that it will push off the image change by 10 seconds each time so the image change will always happen 10 seconds after the last click.

When I pause my timer, then try to start it again, it does not run

I'm building an app in Swift 3. When I press start the first time my timer begins, but when I pause it and try to press start again, the timer does not budge. To give context, the timer, with an amount of time attached to it, is selected from a table. each time the timer load, the start button works initially.
protocol TimerViewControllerDelegate: class {
func viewController(_ controller: ViewController, didFinishEditing item: TaskData)
}
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var pauseButton: UIButton!
#IBOutlet weak var startButton: UIButton!
#IBOutlet weak var timerTaskName: UILabel!
#IBOutlet weak var timerTimeSetting: UILabel!
#IBOutlet weak var progressView: UIProgressView!
weak var delegate: TimerViewControllerDelegate?
var timerTask: TaskData?
var timer: Timer?
var progressViewSpeed: Double = 0.0
#IBAction func cancel(_ sender: Any) {
timer?.invalidate()
dismiss(animated: true, completion: nil)
delegate?.viewController(self, didFinishEditing: timerTask!)
}
#IBAction func startButtonTapped(_ sender: Any) {
timerTask?.startTime = Date()
runTimer()
if timerTask?.isTaskRunning == true {
runTimer()
self.startButton.isEnabled = false
self.pauseButton.isEnabled = true
} else {
//retrieve start time and run
timerTask?.startTime = Date()
runTimer()
self.startButton.isEnabled = false
self.pauseButton.isEnabled = true
}
}
func runTimer() {
guard timer == nil else {
return
}
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
}
#IBAction func pauseButtonTapped(_ sender: UIButton) {
if timerTask?.isTaskRunning == true {
timer?.invalidate()
if let timerTask = timerTask, timerTask.isTaskRunning {
// Calculate the difference between now and when the timerTask was started
let difference = Int(Date().timeIntervalSince(timerTask.startTime!))
timerTask.taskRemaining -= difference
if timerTask.taskRemaining == 0 {
// Do something when there's no time remaining on the task?
}
timerTask.startTime = nil
}
}
else {
timerTask?.startTime = Date()
runTimer()
self.pauseButton.setTitle("Pause",for: .normal)
}
self.startButton.isEnabled = true
self.pauseButton.isEnabled = false
}
/*
#IBAction func resetButtonTapped(_ sender: Any) {
timer.invalidate()
seconds = 60
self.timerLabel.text = timeString(time: TimeInterval(seconds))
if self.resumeTapped == true {
self.resumeTapped = false
self.pauseButton.setTitle("Pause",for: .normal)
}
isTimerRunning = false
pauseButton.isEnabled = false
startButton.isEnabled = true
}
*/
func updateTimer() {
guard let timerTask = timerTask else {
return
}
if timerTask.taskRemaining < 1 {
timer?.invalidate()
timer = nil
//Send alert to indicate "time's up!"
} else {
updateTime()
}
progressViewSpeed = 1 / Double(timerTask.taskRemaining)
progressView.progress += Float(progressViewSpeed)
}
func timeString(time:TimeInterval) -> String {
let hours = Int(time) / 3600
let minutes = Int(time) / 60 % 60
let seconds = Int(time) % 60
return String(format:"%02i:%02i:%02i", hours, minutes, seconds)
}
override func viewDidLoad() {
super.viewDidLoad()
guard let timerTask = timerTask else {
return
}
if timerTask.isTaskRunning {
startButton.isEnabled = false
pauseButton.isEnabled = true
runTimer()
} else {
startButton.isEnabled = true
pauseButton.isEnabled = false
}
timerTaskName.text = timerTask.task
updateTime()
self.progressView.transform = CGAffineTransform.identity.rotated(by: CGFloat.pi / 2).scaledBy(x: 1, y: 150)
}
func updateTime() {
guard let timerTask = timerTask else {
return
}
if let startTime = timerTask.startTime {
// Calculate the difference between now and when the timerTask was started
let difference = Int(Date().timeIntervalSince(startTime))
if timerTask.taskRemaining == difference {
// Do something when there's no time remaining on the task
timer?.invalidate()
timer = nil
}
timerLabel.text = timeString(time: TimeInterval(timerTask.taskRemaining - difference))
} else {
timerLabel.text = timeString(time: TimeInterval(timerTask.taskRemaining))
}
}
}
Once you've invalidated an NSTimer, you can't use it again. You should create the new object.
See here for more From NSTimer Docs
Calling this method requests the removal of the timer from the current run loop; as a result, you should always call the invalidate method from the same thread on which the timer was installed. Invalidating the timer immediately disables it so that it no longer affects the run loop. The run loop then removes and releases the timer, either just before the invalidate method returns or at some later point. Once invalidated, timer objects cannot be reused.
You need to invalidate it and recreate it. "isPaused" bool to keep track of the state
var isPaused = true
var timer: Timer?
#IBAction func pauseResume(sender: AnyObject) {
if isPaused{
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
pauseButton.isHidden = false
startButton.isHidden = true
isPaused = false
} else {
pauseButton.isHidden = true
startButton.isHidden = false
timer.invalidate()
isPaused = true
}
}

IOS swift countdown timer will not stop

I am attempting to make a countdown timer that counts down from 60 seconds and then stops when it gets to 0. But for the timer keeps going into negative seconds. Any advice is appreciated. Code:
#IBOutlet var timeCounter: UILabel!
var second = 60
var timer = NSTimer()
var timerRunning = true
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setTimer()
timerCounting()
}
func setTimer(){
second -= 1
timeCounter.text = "\(second)"
}
func timerCounting(){
if(timerRunning == true){
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("setTimer"), userInfo: nil, repeats: true)
timerRunning = true
if second == 0 {
timerRunning = false
timer.invalidate()
}
}
}
You have to move the invalidation into the setTimer function since at its current location will never be triggered because timerCounting is only called once - in the beginning.
func setTimer(){
second -= 1
timeCounter.text = "\(second)"
if second == 0 {
timerRunning = false
timer.invalidate()
}
}
play with this in playground
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
import Foundation
#objc class C:NSObject {
var timer: NSTimer?
var second = 5
func setTimer(){
if c.second < 0 {
print("the timer fires the last time here ...")
timer?.invalidate()
} else {
print(second)
second -= 1
}
}
}
let c = C()
func timerCounting(){
c.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: c, selector: Selector("setTimer"), userInfo: nil, repeats: true)
}
timerCounting()

Stopwatch app counter going too fast

I am just learning iOS and programming in general and I am making a very basic iOS stopwatch app. I got the stopwatch working however, when I press start more than once the timer begins to go faster so that it is no longer a second long (gif here). Also, my formatting seems to be off for the seconds part, if you have any suggestions there it would be appreciated. Here is my code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var minuteLabel: UILabel!
#IBOutlet weak var secondLabel: UILabel!
var timer = NSTimer()
var second = 0
var minute = 0
func updateTime() {
do{
if second != 59
{
second++
secondLabel.text = ".\(second)"
}
else
{
second = 0
minute++
secondLabel.text = "." + String(format:"$%.2f", second)
if minute < 10
{
minuteLabel.text = "0\(minute)"
}
else
{
minuteLabel.text = String(format:"$%.2f", minute)
}
}
}
}
#IBAction func resetButton(sender: AnyObject) {
timer.invalidate()
second = 0
minute = 0
secondLabel.text = ".00"
minuteLabel.text = "00"
}
#IBAction func stopButton(sender: AnyObject) {
timer.invalidate()
}
#IBAction func startButton(sender: AnyObject) {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateTime"), userInfo: nil, repeats: true)
}
Thank you for your help!
You are not invalidating timer when startButton is called, so tapping "Start" multiple times is creating duplicate timers which call the same function, updateTime. Change startButton to look like this:
#IBAction func startButton(sender: AnyObject) {
if !timer.valid
{
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateTime"), userInfo: nil, repeats: true)
}
}
For your second question about the formatting, you need to put a condition to check for the seconds being less than 10, similar to what you did with the minutes. You would put a 0 in front of the seconds. In updateTime:
if second < 10
{
second++
secondLabel.text = ".0\(second)"
}
else if second <= 59
{
second++
secondLabel.text = ".\(second)"
}
else
{
...
}
See NSTimer documentation for more information.
You can get the desired formatting by using "%02d" instead of %.2f. Using %.2f will ensure 2 digits after the decimal point which is not true in your case (you have both second and minute as integers). Also you can use get away with using one label (e.g., timerLabel) as shown below:
var second = 0
var minute = 0
func updateTime() {
let dFormat = "%02d"
second++
if second == 59{
minute++
second = 0
}
let s = "\String(format: dFormat, minute):\String(format: dFormat, second))"
timerLabel.text = s
}
Hope this helps!

Resources