How to decrease a value constantly when countdown is started? - swift

I want to merge something with this code. My goal is to add a price value in a label which is decreasing constantly ever second when countdown is started. For ex: CurrentPrice - DiscountPerSecond on first second then CurrentPrice - DiscountPerSecond*2 on second etc.. until countdown is finished.
var timerCounter:NSTimeInterval!
func updateTime(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)
let aSelector : Selector = "onTimer:"
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: aSelector, userInfo: nil, repeats: true)
}
func onTimer(timer:NSTimer!) {
countdownShow.text = updateTime(timerCounter)
timerCounter!--
if (timerCounter == 0) {
timer.invalidate()
countdownShow.text = "Event Closed!"
}
}

A few notes:
Instead of counting down from a time interval, you should store a fixed end date and calculate time intervals from the current time as needed.
You should calculate discounts based on a proportion of a maximum discount divided by the proportion of time remaining. (i.e. price = listPrice - (maximumDiscount * proportionOfTimeElapsed))
You should use NSDateComponentsFormatter to format the remaining time.

Related

Convert Minutes to HH:MM:SS - Swift

I have some text fields in an array. HH:MM:SS. I'm having two issues, one is when one of the fields is left blank, the app crashes and I want it to just read as "0" if blank. Second, I use below to convert HH:MM:SS to Minutes. I do 0:18:30 and the math below to decimal comes out to 18.5
Now how would I convert this back to HH:MM:SS? array[0] is hours, array[1] is minutes, array[2] is seconds.
stepOne = (Float(array[0])! * 60) + Float(array[1])! + (Float(array[2])! / 60)
What you need is to calculate the number of seconds instead of number of minutes. Using the reduce method just multiply the first element by 3600 and then divide the multiplier by 60 after each iteration. Next you can use DateComponentsFormatter to display the resulting seconds time interval using .positional units style to the user:
let array = [0,18,30]
var n = 3600
let seconds = array.reduce(0) {
defer { n /= 60 }
return $0 + $1 * n
}
let dcf = DateComponentsFormatter()
dcf.allowedUnits = [.hour, .minute, .second]
dcf.unitsStyle = .positional
dcf.zeroFormattingBehavior = .pad
let string = dcf.string(from: TimeInterval(seconds)) // "00:18:30"

Using perform(afterDelay:) within a while loop gives me a logic error?

I am making a timer program which uses a slider to set a timer value and then updates a digital clock display to show the corresponding numerical time remaining as image files.
I am trying to run a 1 second delay function within a while loop, and update the image files each second, essentially trying to create a timer which updates the variables used to determine which images are used in real time.
I am having difficulty assigning counting down correctly: the variables used to set the image files h, min1 and min2 are set to 0 after a 1 second delay. It seems that the while loop calls the delay function once, iterates until the condition is met without delaying the timer and then displays the final values.
I've tried different methods of timing a 1 second delay including using the let timer = Timer. approach and DispatchQueue.main. approach but they don't seem to work.
#IBAction func slider(_ sender: UISlider)
{
self.x = Int(sender.value)
// Note I omitted the rest of this code as it concerned setting images while changing slider value, and used local variables.
}
var x: Int = 60
var h: Int = 1
var min1: Int = 1
var min2: Int = 7
#objc func animate2 ()
{
checkLbl.text = String(h) + ("1")
checkLbl2.text = String(min1) + ("1")
checkLbl3.text = String(min2) + ("1")
self.H2ImageView.image = UIImage(named: "\(h).png")!
self.M1ImageView.image = UIImage(named: "\(min1).png")!
self.M2ImageView.image = UIImage(named: "\(min2).png")!
}
func animate ()
{
var timeLeft = x
var seconds = 60
while timeLeft >= 0
{
(h, _) = x.quotientAndRemainder(dividingBy: 60)
(min1, min2) = x.quotientAndRemainder(dividingBy: 10)
if min1 >= 6
{
min1 = min1 - 6
if h == 2
{
min1 = 0
}
}
checkLbl.text = String(h)
checkLbl2.text = String(min1)
checkLbl3.text = String(min2)
// checkLbl used to track the variables
perform(#selector(animate2), with: nil, afterDelay: 1)
seconds = seconds - 1
if seconds == 0
{
timeLeft = timeLeft - 1
seconds = 60
}
}
}
#IBAction func watchPress(_ sender: UIButton)
{
animate()
}
To summarise: I expected the delay function to update h, min1 and min2 (and therefore update the checkLbl text) every second however these values go straight to 0.
Any help is appreciated, thanks!
You need to understand that performSelector, DispatchQueue.main.asyncAfter and Timer all run code asynchronously after a certain time. This means that line B won't run one second after line A in this example:
checkLbl3.text = String(min2) // A
perform(#selector(animate2), with: nil, afterDelay: 1)
seconds = seconds - 1 // B
Line B will run immediately after line A. And after one second, animate2 will be called.
Therefore, you should not use a while loop. Instead, you should use a Timer like this:
Timer(timeInterval: 1, repeats: true) { (timer) in
if timeLeft == 0 {
timer.invalidate()
}
seconds -= 1
if seconds == 0
{
timeLeft = timeLeft - 1
seconds = 60
}
// update the labels and image views
}
I also recommend you to only store the time left as a number of seconds. Remove your h, min1, min2 and seconds variables. Only calculate them from the time left when you update the UI.

Get time within radius

Lets imagine we have time line with two points:
A - start time of day
B - end time of day
extension Date: {
var startOfDay: Date {
return Calendar.current.startOfDay(for: self)
}
var endOfDay: Date? {
var components = DateComponents()
components.day = 1
components.second = -1
return Calendar.current.date(byAdding: components, to: startOfDay)
}
}
On the time line there is going one event -> it goes from specifed time, has event time stamp as an interval from 1970 ( event.eventTimestamp ) and there are two options :
It doesn't have end time -> it goes out of bounds after B- point ( duration = 0, to check for it we need to use Int(Date().timeIntervalSince1970) - event.eventTimeStamp
It does have specified end time -> We do have duration.
In both cases It might start few days before A and finish few days after B.
How could I check whenever event is in current day -> specifed by user. After we detect this let's save in variable 24h.
let duration = 24 * 60 * 60
struct Event {
var eventTimestamp: Int
var duration: Int = 0
}

Convert decimal to hours:minutes:seconds

I am storing numbers in a MySQL DB as doubles so I can get min, max and sums.
I have a decimal number 1.66777777778 which equals 01:40:04 however I am wanting to be able to convert this decimal in to hour:minutes:seconds in Swift so I can display the value as 01:40:04 however I don't know how.
I have done some searching but most results are calculators without explanation.
I have this function to convert to decimal:
func timeToHour(hour: String, minute:String, second:String) -> Double
{
var hourSource = 0.00
if hour == ""
{
hourSource = 0.00
}
else
{
hourSource = Double(hour)!
}
let minuteSource = Double(minute)!
let secondSource = Double(second)!
let timeDecimal: Double = hourSource + (minuteSource / 60) + (secondSource / 3600)
return timeDecimal
}
but need one to go back the other way.
Thanks
Try:
func hourToString(hour:Double) -> String {
let hours = Int(floor(hour))
let mins = Int(floor(hour * 60) % 60)
let secs = Int(floor(hour * 3600) % 60)
return String(format:"%d:%02d:%02d", hours, mins, secs)
}
Basically break each component out and concatenate them all together.

AVAudioPlayer currentTime exceeds duration

I'm making my own audio player using AVAudioPlayer.
NOTE: "p" is my instance of the player
Here's how I'm reading the track progress in one of the labels:
currentTime.text = [NSString stringWithFormat:#"%d:%02d", (int)p.currentTime / 60, (int)p.currentTime % 60];
Here's how I set the total duration of the track in one of the labels:
int seconds = (int)p.duration % 60;
int minutes = (int)p.duration / 60;
duration.text = [NSString stringWithFormat:#"%d:%02d", minutes, seconds];
When I run the app on the device, the track's current time ALWAYS exceeds the duration (by about 5-10 seconds).
Is this a bug in AVAudioPlayer, or am I not doing it correctly?
NOTE: This behavior also occurs on the device (not just on the simulator)
After finding the seconds by using % 60, you should remove those seconds when converting the remaining for the minutes. For e.g., with the total duration of 119 seconds after finding 59 seconds you should remove that from 119 and then do minute conversion for 60 seconds (119-59). That might solve your problem.
Minutes should be float: 152 seconds / 60.0f = 2.5333 not 2.
That being said, if you want to show the remaining minutes without the seconds you already obtain: int minutes = (p.duration-seconds) / 60
Also, for a better method to format time the way you want to, have a look at the second answer in this question (not the accepted solution).
Here is the function:
func setTimeString(duration: Double)->String {
var audioDurationSeconds = duration
var expression = ""
var minutesString = "00"
var minutesFloat = 0.0
if (audioDurationSeconds)/60 >= 1 {
minutesFloat = (audioDurationSeconds) / 60
audioDurationSeconds = TimeInterval(Int(audioDurationSeconds)%60)
if minutesFloat < 10.0 {
minutesString = String.init(format: "0%.f", floor(minutesFloat))
} else {
minutesString = String.init(format: "%.f", floor(minutesFloat))
}
}
if audioDurationSeconds < 10.0 {
expression = String.init(format: "%#:0%i", minutesString, Int(audioDurationSeconds))
} else {
expression = String.init(format: "%#:%i", minutesString, (Int(audioDurationSeconds)))
}
return expression
}
extension UILabel{
func getTimeString(from duration:Double) -> String{
let hours = Int(duration / 3600)
let minutes = Int(duration / 60) % 60
let seconds = Int(duration.truncatingRemainder(dividingBy: 60))
if hours > 0 {
return String(format: "%i:%02i:%02i", arguments: [hours,minutes,seconds])
}else {
return String(format: "%02i:%02i", arguments: [minutes,seconds])
}
}