How to create a user defined countdown timer in Swift? - swift

How can I create a user defined countdown timer in Swift?
I got 2 pages in my app. On first page I ask user to write in UITextField a value as hour (for ex: 3), then with segue.identifier, I pass this information to second page, and I want to make a countdown from 3 hours as 02:59:59 format and print it in UILabel. Thanks

I made you a playground with an example of countdown with a NSTimer.
import UIKit
class MyClass {
var timerCounter:NSTimeInterval!
func stringFromTimeInterval(interval: NSTimeInterval) -> String {
let interval = Int(interval)
let seconds = interval % 60
let minutes = (interval / 60) % 60
let hours = (interval / 3600)
return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
}
func startTimer(hour:Int) {
timerCounter = NSTimeInterval(hour * 60 * 60)
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "onTimer:", userInfo: nil, repeats: true)
}
#objc func onTimer(timer:NSTimer!) {
// Here is the string containing the timer
// Update your label here
println(stringFromTimeInterval(timerCounter))
timerCounter!--
}
}
var anInstance = MyClass()
// Start timer with 3 hours
anInstance.startTimer(3)
CFRunLoopRun()

Related

Trying to subtract the input of a UITextField by one but I keep getting an error

My code is here:
#IBAction func updateTime(){
let inputVal: String = textMinutes.text!
var inputInt = Int(inputVal)
timerLabel?.text = "\(inputInt)"
if inputInt != 0 {
inputInt -= 1
}
else {
endTimer()
}
So on the inputInt -=1 I keep getting the error "Cannot convert value of type 'Int?' to expected argument type 'inout Int'". I am not sure how to fix this or if my code is even correct here. This is all for this:
func startTimer() {
countdownTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.updateTime), userInfo: nil, repeats: true)
}
which is for the timer I am trying to make. I am pretty new to this so if there is anything else you need to see I can post it. I've looked all over for solutions but I have not found anything. Thanks in advance.
Your logic is bad. You don't want to change text of some label every second according to same other text every time.
You should create global variable for time, let's say interval
class ViewController: UIViewController {
var interval = 0
...
}
then when you start timer, make sure that user wrote number and if does, start timer and set interval
func startTimer() {
if let interval = Int(textMinutes.text!) {
self.interval = interval
timerLabel?.text = String(interval)
countdownTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.updateTime), userInfo: nil, repeats: true)
} else {
... // handle case if user didn’t write integer
}
}
then every time timer updates, just decrease interval and change text of your text field by this interval variable, then when interval is 0, invalidate your timer
#IBAction func updateTime() {
interval -= 1
timerLabel?.text = String(interval)
if interval == 0 { endTimer() }
}

Adding values to timer in Swift

I am creating a game where the user scores, the timer countdown need to be updated to add 2 seconds to it. I cannot seem to get this working. Other solutions online says that I need to invalidate the current timer and create a new one. How can I do that? The timer starts at 15 seconds, and I need to add 2 seconds in an if condition statement.
#objc func counter(){
seconds -= 1
countDownLabel.text = String(seconds)
if (seconds == 0){
timer.invalidate()
}
}
func updatetimer(){
seconds += 2
}
func activatetimer(){
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.counter), userInfo: nil, repeats: true)
}
I recommend to use a DispatchSource timer which can be restarted reliably right before adding the seconds. If the number of seconds just has expired nothing will happen.
var timer : DispatchSourceTimer?
func activatetimer() {
if timer != nil { return }
timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer!.schedule(deadline:.now() + .seconds(1), repeating: 1.0)
timer!.setEventHandler {
self.seconds -= 1
DispatchQueue.main.async {
self.countDownLabel.text = String(self.seconds)
}
if self.seconds == 0 {
self.timer?.cancel()
self.timer = nil
}
}
timer!.resume()
}
func updatetimer(){
if let timer = timer {
timer.schedule(deadline:.now() + .seconds(1), repeating: 1.0)
seconds += 2
}
}
The Timer is not the key to the problem, the Timer is simply an opportunity to "check" how long the it's been running.
The real key is knowing how long the Timer has been running and how long it should be allowed to run (ie the duration), from this you can calculate how much time is left.
This then allows you to change the duration without having to do anything else.
Please take note that my timer is decreasing
Which is irrelevant. Basically you have several pieces of information...
You know: When the Timer was started
You can calculate: The amount of time the timer has been running (the difference between the start time and now)
You can calculate: The remaining time (difference between running time and the allowable duration)
It's all just basic time/duration functionality. The Timer just provides you means to "check" periodically how long the timer has been running
... As "rough" example...
var startedAt: Date?
var duration: TimeInterval = 5.0
var timer: Timer?
// This is just the label you want to update
#IBOutlet weak var durationLabel: UILabel!
// Convince duration formatter
var durationFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.second]
formatter.unitsStyle = .abbreviated
return formatter
}()
// Stop the clock
func stop() {
timer?.invalidate()
timer = nil
startedAt = nil
}
// Start the clock
func start() {
stop() // Just make sure
startedAt = Date()
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(tick(_:)), userInfo: nil, repeats: true)
}
// The check in
#objc func tick(_ timer: Timer) {
// Is the timer running?
guard let startedAt = startedAt else {
stop()
return
}
// How long has the timer been running
let runningTime = Date().timeIntervalSince(startedAt)
// Has time run out yet
if runningTime >= duration {
durationLabel.text = "Time ran out"
stop()
} else {
// Update the label with the remaining amount of time...
durationLabel.text = durationFormatter.string(from: duration - runningTime)
}
}
// Add more time to the duration...
#IBAction func moreTime(_ sender: Any) {
duration += 2.0
}
Playground example...
This is slightly modified version used to test the idea in Playground
import UIKit
import PlaygroundSupport
class Clock {
var startedAt: Date?
var duration: TimeInterval = 10.0
var timer: Timer?
var durationFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.second]
formatter.unitsStyle = .abbreviated
return formatter
}()
func stop() {
timer?.invalidate()
timer = nil
startedAt = nil
}
func start() {
stop()
startedAt = Date()
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(tick(_:)), userInfo: nil, repeats: true)
}
#objc func tick(_ timer: Timer) {
report()
}
func report() {
guard let startedAt = startedAt else {
stop()
return
}
let runningTime = Date().timeIntervalSince(startedAt)
print(">> Duration = \(duration); Running time = \(runningTime)")
if runningTime >= duration {
print("Time ran out")
PlaygroundPage.current.needsIndefiniteExecution = false
stop()
} else {
print(durationFormatter.string(from: duration - runningTime))
}
}
func moreTime(_ sender: Any) {
duration += 2.0
}
}
PlaygroundPage.current.needsIndefiniteExecution = true
let clock = Clock()
clock.start()
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 2.0) {
print("++")
clock.moreTime("")
clock.report()
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 2.0) {
print("++")
clock.moreTime("")
clock.report()
}
}
When I run it, it outputs...
>> Duration = 10.0; Running time = 1.0034409761428833
Optional("8s")
>> Duration = 10.0; Running time = 2.0030879974365234
Optional("7s")
++
>> Duration = 12.0; Running time = 2.018252968788147
Optional("9s")
>> Duration = 12.0; Running time = 3.002920985221863
Optional("8s")
>> Duration = 12.0; Running time = 4.002920985221863
Optional("7s")
++
>> Duration = 14.0; Running time = 4.035009980201721
Optional("9s")
>> Duration = 14.0; Running time = 5.003154993057251
Optional("8s")
>> Duration = 14.0; Running time = 6.002910017967224
Optional("7s")
>> Duration = 14.0; Running time = 7.002930045127869
Optional("6s")
>> Duration = 14.0; Running time = 8.003202080726624
Optional("5s")
>> Duration = 14.0; Running time = 9.002938032150269
Optional("4s")
>> Duration = 14.0; Running time = 10.002840995788574
Optional("3s")
>> Duration = 14.0; Running time = 11.002991080284119
Optional("2s")
>> Duration = 14.0; Running time = 12.002726078033447
Optional("1s")
>> Duration = 14.0; Running time = 13.003712058067322
Optional("0s")
>> Duration = 14.0; Running time = 14.002851009368896
Time ran out
As you can see, each time moreTime is called (look for the ++ output), 2 seconds is added to the duration. The Clock started with 10 seconds, but ended up running a total of 14 seconds

countdown using NSDate transition to swift 3

Today I switched to Swift 3 language. I am making an app that gives a countdown of days hours minutes and seconds until an event. The following code below displays the following in my app:
Optional(6) days Optional(59) minutes Optional(35) seconds.
My countdown in Swift 2 worked fine but once I converted it to Swift 3 the optional appeared in front of the numbers. Does anyone with experience in Swift 3 know a solution to this problem?
#IBAction func NextWeeklyButtonPressed(_ sender: AnyObject) {
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(printTimeWeeks), userInfo: nil, repeats: true)
timer.fire()
}
func printTimeWeeks() {
formatter.dateFormat = "MM/dd/yy hh:mm:ss a"
let startTime = Date()
let endTime = formatter.date(from: WeeklyDate.text!+" 12:00:00 a")
// WeeklyDate.text is a date in short style
let timeDifference = (userCalendar as NSCalendar).components(requestedComponent, from: startTime, to: endTime!, options: [])
WeeklyDateLabel.text = "\(timeDifference.day) Days \(timeDifference.minute) Minutes \(timeDifference.second) Seconds"
}
append ! to fields
WeeklyDateLabel.text = "\(timeDifference.day!) Days \(timeDifference.minute!) Minutes \(timeDifference.second!) Seconds"
You need to unwrap the optional Strings, e.g.
if let day = timeDifference.day, minute = timeDifference.minute, second = timeDifference.second {
WeeklyDateLabel.text = "\(timeDifference.day) Days \(timeDifference.minute) Minutes \(timeDifference.second) Seconds"
}

How to play a sound/vibrate on a timer if the app is closed without sending a notification?

I am building a workout timer in my app. Workflow is:
User hits "start timer" button
Timer counts down for 90
seconds
Timer ends after 90 seconds and triggers PlayAlertSound
to vibrate
This only works if the app is open, and I don't expect my user to be looking at my app when the timer reaches 0. I can send a notification, but then I'm sending dozens of notifications over the course of a single workout. Personally, I don't like having lots of notifications from a single app. It feels noisy.
Is there a way to have the app send a vibration while the app is closed without sending a notification?
I tried to ask for background resources so my timer runs after closing the app, but even if the timer continues to run, it won't fire the vibration until I open the app, i.e., the user needs to be looking at their phone.
Here is my code:
class TimerViewController: UIViewController {
#IBOutlet weak var startTimerButton: UIButton!
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var resetTimerButton: UIButton!
var timer = NSTimer()
let timeInterval:NSTimeInterval = 0.05
let timerEnd:NSTimeInterval = 90
var timeCount:NSTimeInterval = 0.0
var backgroundTaskIdentifier: UIBackgroundTaskIdentifier?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func startTimerButtonTapped(sender: UIButton) {
backgroundTaskIdentifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
UIApplication.sharedApplication().endBackgroundTask(self.backgroundTaskIdentifier!)
})
if !timer.valid {
timerLabel.text = timeString(timeCount)
timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
target: self,
selector: #selector(TimerViewController.timerDidEnd(_:)),
userInfo: "Time is up!!",
repeats: true) //repeating timer in the second iteration
}
}
#IBAction func resetTimerButtonTapped(sender: UIButton) {
timer.invalidate()
resetTimeCount()
timerLabel.text = timeString(timeCount)
}
func resetTimeCount(){
timeCount = timerEnd
}
func timeString(time:NSTimeInterval) -> String {
let minutes = Int(time) / 60
//let seconds = Int(time) % 60
let seconds = time - Double(minutes) * 60
let secondsFraction = seconds - Double(Int(seconds))
return String(format:"%02i:%02i.%02i",minutes,Int(seconds),Int(secondsFraction * 100.0))
}
func timerDidEnd(timer:NSTimer){
//timerLabel.text = timer.userInfo as? String
//timer that counts down
timeCount = timeCount - timeInterval
if timeCount <= 0 {
timerLabel.text = "Time is up!!"
timer.invalidate()
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
//pushNotification()
} else { //update the time on the clock if not reached
timerLabel.text = timeString(timeCount)
}
}
//
// func pushNotification() {
// let notification = UILocalNotification()
// notification.alertAction = "Go back to App"
// notification.alertBody = "This is a Notification!"
// notification.fireDate = NSDate(timeIntervalSinceNow: 1)
// UIApplication.sharedApplication().scheduleLocalNotification(notification)
// }
//
}
Unfortunately it's not possible to wake up your app when a local notification is received - and that's a good thing, otherwise it would be abused a lot.
But you can play audio in background like this:
How to Play Audio in Background Swift?
Try to play 90 seconds of silence, and then play your alarm sound.

Game Timer using NSDate with NSTimer? swift

I want to start a timer at a specific date and time, then use that start time as a game timer for the rest of the game. Using "timeIntervalSinceDate" will give me seconds but then trying to get the seconds to display on the gameTimerLabel won't work. I might be coming at this the wrong way. Any advice is welcome.
override func viewWillAppear(animated: Bool) {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
let dateAsString1 = "Fri, 1 April 2016 11:30:00 MST"
let date1 = dateFormatter.dateFromString(dateAsString1)!
var currentTime = NSDate()
var counter = 0
gameTimerLabel.text = String(counter)
gameTimerLabel.text = counter //<- Error:Cannot assign value to type 'Int' to type 'String?'
counter = date1.timeIntervalSinceDate(currentTime) //<- Error:Cannot assign value of type 'NSTimeInterval' (aka 'Double') to type 'Int'
}
A couple of things here
First, when you declare counter, it's inferred to be of type Int
var counter = 0
You can declare it as a double by adding a .0 or specifying it's type:
var counter: NSTimeInternval = 0.0
Next, you can use string interoperability to display the count variable in a string, like this:
gameTimerLabel.text = "\(counter)"
Here's an example view controller using an NSTimer as a counter, it counts in seconds:
class ViewController: UIViewController {
// Setup a timer and a counter for use
var timer = NSTimer()
var counter : NSTimeInterval = 0
#IBOutlet weak var label: UILabel!
// Invalidest the timer, resets the counter and udpates the label
#IBAction func resetTimer(sender: AnyObject) {
// Invalidate the timer, reset the label.
self.timer.invalidate()
self.label.text = ""
self.counter = 0
}
// A button that when pressed starts and stops the timer
// There's no pause/resume, so it's invalidated & created again
// But the counter value reamins the same
#IBAction func timerBttnTouched(sender: AnyObject) {
if self.timer.valid {
self.timer.invalidate()
} else {
self.setupTimer()
}
}
// Does the actual counting everytime the timer calls this method
func timerFired() {
self.counter += 1
self.label.text = "\(self.counter)"
}
// Setups a timer, adds it to the run loop and specifies what method should fire when the timer fires
func setupTimer() {
// Setupt the timer, this will call the timerFired method every second
self.timer = NSTimer(
timeInterval: 1,
target: self,
selector: #selector(self.timerFired),
userInfo: nil,
repeats: true)
// Add the timer to the run loop
NSRunLoop.currentRunLoop().addTimer(
self.timer,
forMode: NSDefaultRunLoopMode)
}
}
An important thing to note when using timers is they may not always be called exactly when you need them to, this should be taken into account according to your desired precision when using a timer.
As discussed in comments, here's the solution using a timer to fire a method that compares two dates and uses a NSDateComponentsFormatter to generate a string for display. The initial date is generated in viewDidLoad but can be created anywhere:
class ViewController: UIViewController {
// Setup a timer and a counter for use
var timer = NSTimer()
var counter : NSTimeInterval = 0
var startDate: NSDate?
override func viewDidLoad() {
// Set the initial date
self.startDate = NSDate()
}
#IBOutlet weak var label: UILabel!
// Invalidest the timer, resets the counter and udpates the label
#IBAction func resetTimer(sender: AnyObject) {
// Invalidate the timer, reset the label.
self.timer.invalidate()
self.label.text = ""
self.counter = 0
}
// A button that when pressed starts and stops the timer
// There's no pause/resume, so it's invalidated & created again
// But the counter value reamins the same
#IBAction func timerBttnTouched(sender: AnyObject) {
if self.timer.valid {
self.timer.invalidate()
} else {
self.setupTimer()
}
}
// Does the actual counting everytime the timer calls this method
func timerFired() {
let now = NSDate()
let difference = now.timeIntervalSinceDate(self.startDate!)
// Format the difference for display
// For example, minutes & seconds
let dateComponentsFormatter = NSDateComponentsFormatter()
dateComponentsFormatter.stringFromTimeInterval(difference)
self.label.text = dateComponentsFormatter.stringFromTimeInterval(difference)
}
// Setups a timer, adds it to the run loop and specifies what method should fire when the timer fires
func setupTimer() {
// Setupt the timer, this will call the timerFired method every second
self.timer = NSTimer(
timeInterval: 1,
target: self,
selector: #selector(self.timerFired),
userInfo: nil,
repeats: true)
// Add the timer to the run loop
NSRunLoop.currentRunLoop().addTimer(
self.timer,
forMode: NSDefaultRunLoopMode)
}
}