How to make count down timer with completion handler in swift - ios

Currently I am trying to Create a 3...2...1... Go! type of count down where right after Go! is stated an action occurs however the option to use the completion handler for Timer.scheduleTimer doesn't have the ability to countdown from what I understand?
As of now I can count down from 3 but don't know how to invalidate the timer at Go! and perform an action
var seconds = 3
var countDownTimer = Timer()
func startCountDown() {
countDownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(TrackActivityVC.updateTimer), userInfo: nil, repeats: true)
}
func updateTimer() {
seconds -= 1
print(seconds)
}
// Not sure how to use this code to create a countdown, as it doesn't seem possible with this method
Timer.scheduledTimer(withTimeInterval: someInterval, repeats: false) {
}
Thus the question is How to make count down timer with completion handler.

Here is how to create a countdown timer using the trailing closure syntax:
var seconds = 3
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
self.seconds -= 1
if self.seconds == 0 {
print("Go!")
timer.invalidate()
} else {
print(self.seconds)
}
}
Here is a better version that invalidates the timer when the viewController is deinitialized. It uses [weak self] to avoid hanging on to the viewController if it closes:
class MyViewController: UIViewController {
var seconds = 3
var myTimer: Timer?
func startCountdown() {
myTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
self?.seconds -= 1
if self?.seconds == 0 {
print("Go!")
timer.invalidate()
} else if let seconds = self?.seconds {
print(seconds)
}
}
}
deinit {
// ViewController going away. Kill the timer.
myTimer?.invalidate()
}
}

Related

Can't get timer to count down

I'm trying to learn how to use a timer in Swift, and every solution I look up is broken somehow or beyond my understanding.
I've tried with a closure, and without.
With a closure, I can actually get the app to run without crashing, but the timer just repeats 60, it doesn't count down and I don't know why.
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
var secondsRemaining = 60
print(secondsRemaining)
secondsRemaining -= 1
})
I've also tried using an #objc func with selector, but my app crashes right away with error Thread 1: "-[__SwiftValue countdown]: unrecognized selector sent to instance 0x600002970e40" (I haven't even gotten to trying the count down yet).
class ViewController: UIViewController {
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdown), userInfo: nil, repeats: true)
#objc func countdown() {
print("fire")
}
…
…
}
Any help is appreciated.
EDIT
If I place my variable outside the block, I get an error inside the block Instance member 'secondsRemaining' cannot be used on type 'ViewController'
class ViewController: UIViewController {
var secondsRemaining = 60
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
print(secondsRemaining)
secondsRemaining -= 1
})
}
But if I create a new project and put the timer inside viewDidLoad(), it works. I don't know why.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var secondsRemaining = 60
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
print(secondsRemaining)
secondsRemaining -= 1
})
}
}
Every time your timer fires, a new variable with an initial value of 60 is instantiated, thus always printing 60.
You have to declare your counting variable outside:
private var secondsRemaining = 60
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
print(self.secondsRemaining)
self.secondsRemaining -= 1
})
Or:
class ViewController: UIViewController {
private var secondsRemaining = 60
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdown), userInfo: nil, repeats: true)
#objc func countdown() {
print("fire")
self.secondsRemaining -= 1
}
}
You're declaring counter inside the timer call. That's why every time the timer executes it resets the counter to 60 seconds. You need to declare your timer outside the it's call:
var secondsRemaining = 60
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
print(secondsRemaining)
secondsRemaining -= 1
})
As mentioned in the comments above, don't reset the variable every single time you get in the closure and change you timer to the following. Create a simple obj function to do whatever logic you want. Also name your functionalists meaningfully
class XViewController: UIViewController {
private var secondsRemaining:Int = 60
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(startScrolling), userInfo: nil, repeats: true)
#objc func startScrolling() {
print(secondsRemaining)
secondsRemaining -= 1
}
}
Here is the fixed code:
class ViewController: UIViewController {
private var secondsRemaining = 60
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
print(secondsRemaining)
secondsRemaining -= 1
})
}
}

Vibrate indefinitely in Swift

How can I make an iPhone vibrate indefinitely? I know I can do this:
while true {
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
}
Is there any way to play the vibration for a certain number of seconds, or something with a similar result that is cleaner? Also, is there a way to make the vibration more intense?
I think you should use Timer.
var timer: Timer?
var numberRepeat = 0
var maxNumberRepeat = 20
func startTimer() {
//
timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(self.timerHandler), userInfo: nil, repeats: true)
}
func stopTimer() {
timer?.invalidate()
timer = nil
}
#objc func timerHandler() {
if numberRepeat <= maxNumberRepeat {
numberRepeat += 1
vibrateDevice()
} else {
stopTimer()
}
}
func vibrateDevice() {
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
}

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

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

Create a UIProgressView to go from 0.0 (empty) to 1.0 (full) in 3 seconds

Basically I want to create a UIProgressView to go from 0.0 (empty) to 1.0 (full) in 3 seconds. Could anyone point me in the right direction for using NSTimer in swift with UIProgressView?
If anyone's interested here is a Swift 5 working version :
extension UIProgressView {
#available(iOS 10.0, *)
func setAnimatedProgress(progress: Float = 1, duration: Float = 1, completion: (() -> ())? = nil) {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
DispatchQueue.main.async {
let current = self.progress
self.setProgress(current+(1/duration), animated: true)
}
if self.progress >= progress {
timer.invalidate()
if completion != nil {
completion!()
}
}
}
}
}
Usage :
// Will fill the progress bar in 70 seconds
self.progressBar.setAnimatedProgress(duration: 70) {
print("Done!")
}
I created my own solution after searching for one and coming across this question.
extension UIProgressView {
func setAnimatedProgress(progress: Float,
duration: NSTimeInterval = 1,
delay: NSTimeInterval = 0,
completion: () -> ()
) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
sleep(UInt32(delay))
dispatch_async(dispatch_get_main_queue()) {
self.layer.speed = Float(pow(duration, -1))
self.setProgress(progress, animated: true)
}
sleep(UInt32(duration))
dispatch_async(dispatch_get_main_queue()) {
self.layer.speed = 1
completion()
}
}
}
}
Declare the properties:
var time = 0.0
var timer: NSTimer
Initialize the timer:
timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector:Selector("setProgress"), userInfo: nil, repeats: true)
Implement the setProgress function:
func setProgress() {
time += 0.1
dispatch_async(dispatch_get_main_queue(), {
progressView.progress = time / 3
})
if time >= 3 {
timer.invalidate()
}
}
(I'm not 100% sure if the dispatch block is necessary, to make sure the UI is updated in the main thread. Feel free to remove this if it isn't necessary.)

Resources