How do I get the progress bar to work in my iOS app? - ios

I made a timer in Swift using the following code:
import UIKit
class ViewController: UIViewController {
// #IBOutlet var ProgressBar: UIProgressView!
#IBOutlet var label: UILabel!
var hours: Int = 0
var mins: Int = 0
var secs: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
//ProgressBar.progress = 0.0
}
#IBAction func didTapAddButton() {
// var progress: Float = 0.0
//ProgressBar.progress = progress
let vc = storyboard?.instantiateViewController(identifier:"date_picker") as! DateViewController
vc.title = "New Event"
vc.completionHandler = { [weak self] name, date in
DispatchQueue.main.async {
self?.didCreateEvent(name: name, targetDate: date)
}
}
navigationController?.pushViewController(vc, animated: true)
}
private func didCreateEvent(name: String, targetDate: Date){
self.title = name
let difference = floor(targetDate.timeIntervalSince(Date()))
if difference > 0.0 {
let computedHours: Int = Int(difference) / 3600
let remainder: Int = Int(difference) - (computedHours * 3600)
let minutes: Int = remainder / 60
let seconds: Int = Int(difference) - (computedHours * 3600) - (minutes * 60)
print("\(computedHours) \(minutes) \(seconds)")
hours = computedHours
mins = minutes
secs = seconds
updateLabel()
startTimer()
}
else{
print("negative interval")
}
}
private func startTimer() {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in
if self.secs > 0 {
self.secs = self.secs - 1
}
else if self.mins > 0 && self.secs == 0 {
self.mins = self.mins - 1
self.secs = 59
}
else if self.hours > 0 && self.mins == 0 && self.secs == 0 {
self.hours = self.hours - 1
self.mins = 59
self.secs = 59
}
self.updateLabel()
})
}
private func updateLabel() {
label.text = "\(hours):\(mins):\(secs)"
}
}
I am trying to make a progress bar somewhat show how much time is left. I want the bar to be full when there is no time left. How should I do this?
I tried searching how to do this but Google wasn't helpful.

So the first thing you want to do is bring all your values for hour, minutes, and seconds into a singular combined value, represented as something like totalSeconds or whatever you want.
var hours: Int = XXX
var minutes: Int = XXX
var seconds: Int = XXX
var totalSeconds: Int = 0
totalSeconds = getTotalSecondsFrom(hours, minutes, seconds)
func getTotalSecondsFrom(hours: Int, minutes: Int, seconds: Int) -> Int {
let hoursToSec = (hours * 60) * 60
let minutesToSec = minutes * 60
return hoursToSec + minutesToSec + seconds
}
Once you have all of your values brought down into a singular value now you have a representation for calculating a progress bar's maximum limit. Remember that a progress bar can only have values betweeen 0.0 and 1.0 which is perfect for a percentage calculation. In the interest of explaining in a way that will make sense for learning, let's look at this from a different perspective. How can we get a percentage from a total maximum, then map it to a value in the range of 0.0 -> 1.0?
func getCompletedPercent(currentSeconds: Int, totalSeconds: Int) -> Float {
return Float(currentSeconds) / Float(totalSeconds)
}
Why does this work? Well, let's assume our total seconds are 5000 and current is 500. Doing the math, 500 / 5000 = 0.1 which is 1/10th or 10% of 5000, which is exactly what we're looking for. With this knowledge in hand all you need to do at this point is set your progress bars maximum to 1.0 and then every second, in your timer, update it's current progress using the functional examples getTotalSecondsFrom(...) and getCompletedPercent(...). Of course your version may vary slightly, but the concepts are the same.

Related

Swift - Re-add time back into Timer

I have a countdown Timer that shows seconds and milliseconds. The user can start/stop recording multiple times until the timer hits zero. The user can also delete a previous recording at which point I have to re-add that deleted time back into the initial 20 secs. There are 2 issues.
The first issue is when the timer is stopped, the remaining time that shows on the timer label doesn't match the time culmination of the recordings. From my understanding this might be a RunLoop issue and I don't think there is anything that I can do about the inaccuracies.
let initialTime = 20.0
var cumulativeTimeForAllAssests = 0.0
for asset in arrOfAssets {
let assetDuration = CMTimeGetSeconds(asset.duration)
print("assetDuration: ", assetDuration)
cumulativeTimeForAllAssests += assetDuration
}
print("\ncumulativeTimeForAllAssests: ", cumulativeTimeForAllAssests)
After starting/stopping 5 times, the remaining time on the timer label says 16.5 but the culmination of the assets time is 4.196666.... The timer label should say 15.8, it's 0.7 milli off. The more I start/stop the recording, the more inaccurate/further off the culmination time - the initial time and the timer label time is.
assetDuration: 0.7666666666666667
assetDuration: 0.9666666666666667
assetDuration: 0.7983333333333333
assetDuration: 0.7333333333333333
assetDuration: 0.9316666666666666
cumulativeTimeForAllAssests: 4.196666666666667
The second issue is because I'm using seconds and milliseconds in my timerLabel, when I add re-add the subtracted time back in via deleteAssetAndUpdateTimer(...), I use the parts of modf() to update the seconds and milliseconds. I couldn't think of another way to update the timer. I know there has to be a more accurate way to do it.
Timer code:
weak var timer: Timer?
var seconds = 20
var milliseconds = 0
let initialTime = 20.0
func startTimer() {
invalidateTimer()
if seconds == Int(initalTime) && milliseconds == 0 {
timerIsRunning()
}
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [weak self] _ in
self?.timerIsRunning()
})
}
func timerIsRunning() {
updateTimerLabel()
if milliseconds == 0 {
seconds -= 1
}
milliseconds -= 1
if milliseconds < 0 {
milliseconds = 9
}
if seconds == 0 && milliseconds == 0 {
invalidateTimer()
updateTimerLabel()
}
}
func invalidateTimer() {
timer?.invalidate()
timer = nil
}
func updateTimerLabel() {
let milisecStr = "\(milliseconds)"
let secondsStr = seconds > 9 ? "\(seconds)" : "0\(seconds)"
timerLabel.text = "\(secondsStr).\(milisecStr)"
}
Delete asset and update timer code:
// the timer is stopped when this is called
func deleteAssetAndUpdateTimer(_ assetToDelete: AVURLAsset) {
var cumulativeTimeForAllAssests = 0.0
for asset in arrOfAssets {
let assetDuration = CMTimeGetSeconds(asset.duration)
cumulativeTimeForAllAssests += assetDuration
}
let timeFromAssetToDelete = CMTimeGetSeconds(assetToDelete.duration)
let remainingTime = self.initialTime - cumulativeTimeForAllAssests
let updatedTime = remainingTime + timeFromAssetToDelete
let mod = modf(updatedTime)
self.seconds = Int(mod.0)
self.milliseconds = Int(mod.1 * 10)
updateTimerLabel()
// remove assetToDelete from array
}
The big issue here was I was using a Timer to countdown which was incorrect. Following #LeoDabus' comments, I instead used CACurrentMediaTime():
let timerLabel = UILabel()
let maxRecordingTime = 30.0
lazy var elapsedTime = maxRecordingTime
var startTime: CFTimeInterval?
var endTime: CFTimeInterval?
weak var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
updateTimerLabel(with: Int(maxRecordingTime))
}
#IBAction func recordButtonPressed(_ sender: UIButton) {
if startTime == nil {
startTimer()
} else {
stopTimer(updateElapsed: true)
}
}
func startTimer() {
if elapsedTime == 0 { return }
stopTimer()
startTime = CACurrentMediaTime()
endTime = startTime! + elapsedTime
print("startTime: \(startTime!) | endTime: \(endTime!)")
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { [weak self] _ in
self?.timerIsRunning()
}
}
func timerIsRunning() {
guard let startTime = startTime, let endTime = endTime else { return }
let currentTime = CACurrentMediaTime()
let remainingTime = currentTime - startTime
print("%2d %.3lf", elapsedTime, remainingTime)
if currentTime >= endTime {
print("stopped at - currentTime: \(currentTime) | endTime: \(endTime)")
stopTimer(updateElapsed: true, currentTime: currentTime)
return
}
let countDownTime: Double = elapsedTime - remainingTime
let seconds = Int(countDownTime)
updateTimerLabel(with: seconds)
}
func updateTimerLabel(with seconds: Int) {
let secondsStr = seconds > 9 ? "\(seconds)" : "0\(seconds)"
timerLabel.text = secondsStr
}
func stopTimer(updateElapsed: Bool = false, currentTime: Double? = nil) {
timer?.invalidate()
timer = nil
if updateElapsed {
updateElapsedTime(using: currentTime)
}
startTime = nil
endTime = nil
}
func updateElapsedTime(using currentTime: Double? = nil) {
guard let startTime = startTime else { return }
var timeNow = CACurrentMediaTime()
if let currentTime = currentTime {
timeNow = currentTime
}
var updatedTime = elapsedTime - (timeNow - startTime)
if updatedTime < 0 {
updatedTime = 0
}
elapsedTime = updatedTime
}
func resetElapsedTime() { // This is for a resetButton not shown here
elapsedTime = maxRecordingTime
}

How to Display Best Time in an iOS Game with 3 Significant Digits Decimal Value?

I am saving the best time in my iOS Game using the following functions:-
func format(timeInterval: TimeInterval) -> String {
let interval = Int(timeInterval)
let seconds = interval % 60
let minutes = (interval / 60) % 60
let milliseconds = Int(timeInterval * 1000) % 1000
return String(format: "%02d:%02d.%03d", minutes, seconds, milliseconds)
}
func setBestTime(with time: Int){
let defaults = UserDefaults.standard
let previousBestTime = defaults.integer(forKey: "bestTime")
defaults.set(time > previousBestTime ? time : previousBestTime, forKey: "bestTime")
}
func getBestTime(){
self.bestTimeLabel.text = "\(UserDefaults.standard.integer(forKey: "bestTime"))"
}
func gameOver() {
stopGame()
setBestTime(with: Int(elapsedTime))
}
But, it displays the best time in integer. I want the best time to be displayed in decimal with 3 significant figures. Could anyone please let me know how can I do that? Thanks for the help!
You aren't calling your format function. You simply need to pass the Int value retrieve from UserDefault to format before displaying it on your label.
You should also use UserDefaults.double if you want to store a TimeInterval rather than an Int.
let defaults = UserDefaults.standard
let bestTimeKey = "bestTime"
func format(timeInterval: TimeInterval) -> String {
let interval = Int(timeInterval)
let seconds = interval % 60
let minutes = (interval / 60) % 60
let milliseconds = Int(timeInterval * 1000) % 1000
return String(format: "%02d:%02d.%03d", minutes, seconds, milliseconds)
}
func setBestTime(with time: TimeInterval){
let previousBestTime = defaults.double(forKey: bestTimeKey)
defaults.set(max(time, previousBestTime), forKey: bestTimeKey)
}
func getBestTime() {
let bestTime = defaults.double(forKey: bestTimeKey)
let formattedBestTime = format(timeInterval: bestTime)
bestTimeLabel.text = formattedBestTime
}
func gameOver() {
stopGame()
setBestTime(with: elapsedTime)
}

how to remove cell index when timer gets complete after 5 min ios swift 5 , when called api not repeat timeragain of same index

I want to implement timer logic, when 5 min gets complete then my Tableview reload and its remove that particular index, I have tried not gets works, and timer get fast
//Timer ACtion Method
#objc func timerAction() {
if seconds>0 {
seconds-=1
minutes = String(format:"%02i",(seconds / 60))
seconds1 = String(format:"%02i",(seconds % 60))
print(minutes! + ":" + seconds1!)
self.lblMin.text = minutes!
self.lblSec.text = seconds1!
} else {
minutes = String(seconds / 60)
seconds1 = String(seconds % 60)
if minutes == "0" && seconds1 == "0" {
timer.invalidate()
btnReject.isUserInteractionEnabled = false
btnAccept.isUserInteractionEnabled = false
// TBVC.InstancePending.arrPending.remove(at: intValue!)
//tblData?.deleteRows(at: [IndexPath(row: intValue!, section: 1)], with: .automatic)
// TBVC.InstancePending.getTableBooking(strStatus: "0")
// TBVC.InstancePending.strTap = "Pending"
// TBVC.InstancePending.segment.selectedSegmentIndex = 0
// tblData?.reloadData()
}
}
}
Set Timer value nil, And check when API called then the timer will not pass any selector method
#objc func timerAction() {
if seconds>0 {
seconds-=1
minutes = String(format:"%02i",(seconds / 60))
seconds1 = String(format:"%02i",(seconds % 60))
print(minutes! + ":" + seconds1!)
self.lblMin.text = minutes!
self.lblSec.text = seconds1!
} else {
minutes = String(seconds / 60)
seconds1 = String(seconds % 60)
if minutes == "0" && seconds1 == "0" {
timer.invalidate()
timer = nil
btnReject.isUserInteractionEnabled = false
btnAccept.isUserInteractionEnabled = false
// TBVC.InstancePending.arrPending.remove(at: intValue!)
//tblData?.deleteRows(at: [IndexPath(row: intValue!, section: 1)], with: .automatic)
// TBVC.InstancePending.getTableBooking(strStatus: "0")
// TBVC.InstancePending.strTap = "Pending"
// TBVC.InstancePending.segment.selectedSegmentIndex = 0
// tblData?.reloadData()
}
}
}
=====================================
2nd method to implement timer:-
Initialize Variable
var timer:Timer?
var totalMinut:Int = 2
var totalSecond:Int = 120
var timeLeft = 120
Add timer function
func setupTimer() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(onTimerFires), userInfo: nil, repeats: true)
}
#objc func onTimerFires() {
var minutes: Int
var seconds: Int
if totalSecond == 1 {
timer?.invalidate()
timer = nil
}
totalSecond = totalSecond - 1
minutes = (totalSecond) / 60
seconds = (totalSecond) % 60
timerLabel.text = String(format: "%02d:%02d", minutes, seconds)
}
Call "setUpTimer" method where you have required. In my case, I have called it in the "viewDidLoad" method of a view controller
override func viewDidLoad() {
super.viewDidLoad()
setupTimer()
}

Why is the function to update my progress slider called 3 times using addPeriodicTimeObserverForInterval in AVPlayer?

Hello I've been trying to figure out this issue and don't know where to look. I'm using AVPlayer to play videos, and I have a UISlider whose value updates every second based on the progresss of the video using addPeriodicTimeObserverForInterval.
When I press pause, the UISlider thumb stops immediately as expected. However, immediately after I press play to resume the video, the thumb moves slightly before it continues progressing as normal every second.
I can't figure out why it's doing this. I'd like to have the UISlider thumb progress fluently, i.e. if I paused the video at 1.5 seconds in, and I play the video again, it SHOULD wait 0.5 seconds before the thumb moves again.
Here's a snippet of my code:
override func viewDidLoad()
{
super.viewDidLoad()
....
isPlaybackSliderTouched = false
isPauseButtonTouched = false
setUpPlayerControls()
....
}
override func viewDidAppear(animated: Bool)
{
super.viewDidAppear(true)
let timeIntervalOne: CMTime = CMTimeMakeWithSeconds(1.0, 10)
playbackSliderTimer = avPlayer.addPeriodicTimeObserverForInterval(timeIntervalOne,
queue: dispatch_get_main_queue()) { (elapsedTime: CMTime) -> Void in
self.observeTime(elapsedTime)
}
....
playPauseButton.addTarget(self, action: "onClick:", forControlEvents: .TouchUpInside)
}
func observeTime(elapsedTime: CMTime)
{
let duration = CMTimeGetSeconds(avPlayer.currentItem!.duration)
if isfinite(duration) && avPlayer.rate == 1
{
print("ENNNTNTTERRR")
let elapsedTime = CMTimeGetSeconds(elapsedTime)
updatePlaybackSlider(elapsedTime: elapsedTime, duration: duration)
}
}
func updatePlaybackSlider(elapsedTime: Float64, duration: Float64)
{
if isPlaybackSliderTouched == false
{
let sliderValue: Float = Float(elapsedTime / duration)
print("sliderValue = \(sliderValue)")
self.playbackSlider.setValue(sliderValue, animated: true)
self.currentTimeLabel.text = self.convertSecondsToHHMMSS(elapsedTime)
self.endTimeLabel.text = self.convertSecondsToHHMMSS(duration - elapsedTime)
print("currentTimeLabel.text = \(currentTimeLabel.text)")
print("endTimeLabel.text = \(endTimeLabel.text)")
}
}
func convertSecondsToHHMMSS(seconds: Float64) -> String
{
let time: Int = Int( floor(seconds) )
print("time = \(time)")
let hh: Int = time / 3600
let mm: Int = (time / 60) % 60
let ss: Int = time % 60
print("seconds = \(ss)")
if hh > 0
{
return String(format: "%02d:%02d:%02d", hh, mm, ss)
}
else
{
return String(format: "%02d:%02d", mm, ss )
}
}
deinit
{
avPlayer.removeTimeObserver(playbackSliderTimer)
}
func onClick(sender: UIButton)
{
print("onClick")
if sender == playPauseButton
{
print("playPauseButton touched")
let playerIsPlaying:Bool = avPlayer.rate > 0
if (playerIsPlaying)
{
isPauseButtonTouched = true
avPlayer.pause()
sender.selected = true
}
else
{
isPauseButtonTouched = false
avPlayer.play()
sender.selected = false
}
}
}
Here's a sample output immediately after I press pause from play state:
setUpPlayerControls
viewDidAppear
ENNNTNTTERRR
sliderValue = 0.0
time = 0
seconds = 0
time = 39
seconds = 39
currentTimeLabel.text = Optional("00:00")
endTimeLabel.text = Optional("-00:39")
ENNNTNTTERRR
sliderValue = 4.76564e-05
time = 0
seconds = 0
time = 39
seconds = 39
currentTimeLabel.text = Optional("00:00")
endTimeLabel.text = Optional("-00:39")
ENNNTNTTERRR
sliderValue = 0.0252767
time = 1
seconds = 1
time = 38
seconds = 38
currentTimeLabel.text = Optional("00:01")
endTimeLabel.text = Optional("-00:38")
ENNNTNTTERRR
sliderValue = 0.0505207
time = 2
seconds = 2
time = 37
seconds = 37
currentTimeLabel.text = Optional("00:02")
endTimeLabel.text = Optional("-00:37")
handleSingleTap
onClick
playPauseButton touched
pause touched
Here's the continuing output when I tap the play from pause state:
onClick
playPauseButton touched
play touched
ENNNTNTTERRR
sliderValue = 0.0718539
time = 2
seconds = 2
time = 36
seconds = 36
currentTimeLabel.text = Optional("00:02")
endTimeLabel.text = Optional("-00:36")
ENNNTNTTERRR
sliderValue = 0.0722224
time = 2
seconds = 2
time = 36
seconds = 36
currentTimeLabel.text = Optional("00:02")
endTimeLabel.text = Optional("-00:36")
ENNNTNTTERRR
sliderValue = 0.0757659
time = 3
seconds = 3
time = 36
seconds = 36
currentTimeLabel.text = Optional("00:03")
endTimeLabel.text = Optional("-00:36")
ENNNTNTTERRR
sliderValue = 0.101012
time = 4
seconds = 4
time = 35
seconds = 35
currentTimeLabel.text = Optional("00:04")
endTimeLabel.text = Optional("-00:35")
As you can see the end of the first output when I press pause to the beginning of the second output when I press play, there is 2 unnecessary calls for time 00:02 to observeTime(elapsedTime), which in turns call my updatePlaybackSlider, which in turn slightly shifts the slider value to the right a bit before it goes back to calling observeTime(elapsedTime) like normal, i.e. every 1 second.
What are some suggestions for me to fix this?
Thanks.

Swift - Integer conversion to Hours/Minutes/Seconds

I have a (somewhat?) basic question regarding time conversions in Swift.
I have an integer that I would like converted into Hours / Minutes / Seconds.
Example: Int = 27005 would give me:
7 Hours 30 Minutes 5 Seconds
I know how to do this in PHP, but alas, swift isn't PHP.
Define
func secondsToHoursMinutesSeconds(_ seconds: Int) -> (Int, Int, Int) {
return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)
}
Use
> secondsToHoursMinutesSeconds(27005)
(7,30,5)
or
let (h,m,s) = secondsToHoursMinutesSeconds(27005)
The above function makes use of Swift tuples to return three values at once. You destructure the tuple using the let (var, ...) syntax or can access individual tuple members, if need be.
If you actually need to print it out with the words Hours etc then use something like this:
func printSecondsToHoursMinutesSeconds(_ seconds: Int) {
let (h, m, s) = secondsToHoursMinutesSeconds(seconds)
print ("\(h) Hours, \(m) Minutes, \(s) Seconds")
}
Note that the above implementation of secondsToHoursMinutesSeconds() works for Int arguments. If you want a Double version you'll need to decide what the return values are - could be (Int, Int, Double) or could be (Double, Double, Double). You could try something like:
func secondsToHoursMinutesSeconds(seconds: Double) -> (Double, Double, Double) {
let (hr, minf) = modf(seconds / 3600)
let (min, secf) = modf(60 * minf)
return (hr, min, 60 * secf)
}
In macOS 10.10+ / iOS 8.0+ (NS)DateComponentsFormatter has been introduced to create a readable string.
It considers the user's locale und language.
let interval = 27005
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.unitsStyle = .full
let formattedString = formatter.string(from: TimeInterval(interval))!
print(formattedString)
The available unit styles are positional, abbreviated, short, full, spellOut and brief.
For more information please read the documenation.
Building upon Vadian's answer, I wrote an extension that takes a Double (of which TimeInterval is a type alias) and spits out a string formatted as time.
extension Double {
func asString(style: DateComponentsFormatter.UnitsStyle) -> String {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second, .nanosecond]
formatter.unitsStyle = style
return formatter.string(from: self) ?? ""
}
}
Here are what the various DateComponentsFormatter.UnitsStyle options look like:
10000.asString(style: .positional) // 2:46:40
10000.asString(style: .abbreviated) // 2h 46m 40s
10000.asString(style: .short) // 2 hr, 46 min, 40 sec
10000.asString(style: .full) // 2 hours, 46 minutes, 40 seconds
10000.asString(style: .spellOut) // two hours, forty-six minutes, forty seconds
10000.asString(style: .brief) // 2hr 46min 40sec
In Swift 5:
var i = 9897
func timeString(time: TimeInterval) -> String {
let hour = Int(time) / 3600
let minute = Int(time) / 60 % 60
let second = Int(time) % 60
// return formated string
return String(format: "%02i:%02i:%02i", hour, minute, second)
}
To call function
timeString(time: TimeInterval(i))
Will return 02:44:57
I have built a mashup of existing answers to simplify everything and reduce the amount of code needed for Swift 3.
func hmsFrom(seconds: Int, completion: #escaping (_ hours: Int, _ minutes: Int, _ seconds: Int)->()) {
completion(seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)
}
func getStringFrom(seconds: Int) -> String {
return seconds < 10 ? "0\(seconds)" : "\(seconds)"
}
Usage:
var seconds: Int = 100
hmsFrom(seconds: seconds) { hours, minutes, seconds in
let hours = getStringFrom(seconds: hours)
let minutes = getStringFrom(seconds: minutes)
let seconds = getStringFrom(seconds: seconds)
print("\(hours):\(minutes):\(seconds)")
}
Prints:
00:01:40
Here is a more structured/flexible approach: (Swift 3)
struct StopWatch {
var totalSeconds: Int
var years: Int {
return totalSeconds / 31536000
}
var days: Int {
return (totalSeconds % 31536000) / 86400
}
var hours: Int {
return (totalSeconds % 86400) / 3600
}
var minutes: Int {
return (totalSeconds % 3600) / 60
}
var seconds: Int {
return totalSeconds % 60
}
//simplified to what OP wanted
var hoursMinutesAndSeconds: (hours: Int, minutes: Int, seconds: Int) {
return (hours, minutes, seconds)
}
}
let watch = StopWatch(totalSeconds: 27005 + 31536000 + 86400)
print(watch.years) // Prints 1
print(watch.days) // Prints 1
print(watch.hours) // Prints 7
print(watch.minutes) // Prints 30
print(watch.seconds) // Prints 5
print(watch.hoursMinutesAndSeconds) // Prints (7, 30, 5)
Having an approach like this allows the adding of convenience parsing like this:
extension StopWatch {
var simpleTimeString: String {
let hoursText = timeText(from: hours)
let minutesText = timeText(from: minutes)
let secondsText = timeText(from: seconds)
return "\(hoursText):\(minutesText):\(secondsText)"
}
private func timeText(from number: Int) -> String {
return number < 10 ? "0\(number)" : "\(number)"
}
}
print(watch.simpleTimeString) // Prints 07:30:05
It should be noted that purely Integer based approaches don't take leap day/seconds into account. If the use case is dealing with real dates/times Date and Calendar should be used.
Swift 5:
extension Int {
func secondsToTime() -> String {
let (h,m,s) = (self / 3600, (self % 3600) / 60, (self % 3600) % 60)
let h_string = h < 10 ? "0\(h)" : "\(h)"
let m_string = m < 10 ? "0\(m)" : "\(m)"
let s_string = s < 10 ? "0\(s)" : "\(s)"
return "\(h_string):\(m_string):\(s_string)"
}
}
Usage:
let seconds : Int = 119
print(seconds.secondsToTime()) // Result = "00:01:59"
Swift 4
func formatSecondsToString(_ seconds: TimeInterval) -> String {
if seconds.isNaN {
return "00:00"
}
let Min = Int(seconds / 60)
let Sec = Int(seconds.truncatingRemainder(dividingBy: 60))
return String(format: "%02d:%02d", Min, Sec)
}
SWIFT 3.0 solution based roughly on the one above using extensions.
extension CMTime {
var durationText:String {
let totalSeconds = CMTimeGetSeconds(self)
let hours:Int = Int(totalSeconds.truncatingRemainder(dividingBy: 86400) / 3600)
let minutes:Int = Int(totalSeconds.truncatingRemainder(dividingBy: 3600) / 60)
let seconds:Int = Int(totalSeconds.truncatingRemainder(dividingBy: 60))
if hours > 0 {
return String(format: "%i:%02i:%02i", hours, minutes, seconds)
} else {
return String(format: "%02i:%02i", minutes, seconds)
}
}
}
Use it with AVPlayer calling it like this?
let dTotalSeconds = self.player.currentTime()
playingCurrentTime = dTotalSeconds.durationText
Here is another simple implementation in Swift3.
func seconds2Timestamp(intSeconds:Int)->String {
let mins:Int = intSeconds/60
let hours:Int = mins/60
let secs:Int = intSeconds%60
let strTimestamp:String = ((hours<10) ? "0" : "") + String(hours) + ":" + ((mins<10) ? "0" : "") + String(mins) + ":" + ((secs<10) ? "0" : "") + String(secs)
return strTimestamp
}
Xcode 12.1. Swift 5
DateComponentsFormatter: A formatter that creates string representations,
by using unitsStyle u can get a string as you want and mention allowedUnits.
e.g: output for unitsStyle:: for 10000 secods
full = "2 hours, 46 minutes, 49 seconds"
positional = "2:46:40"
abbreviated = "2h 46m 40s"
spellOut = "two hours, forty-six minutes, forty seconds”
short = "2hr,46 min,40 sec"
brief = "2hr 46min 40sec"
Easy to use:
let time = convertSecondsToHrMinuteSec(seconds: 10000)
func convertSecondsToHrMinuteSec(seconds:Int) -> String{
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.unitsStyle = .full
let formattedString = formatter.string(from:TimeInterval(seconds))!
print(formattedString)
return formattedString
}
I had answered to the similar question, however you don't need to display milliseconds in the result. Hence my solution requires iOS 10.0, tvOS 10.0, watchOS 3.0 or macOS 10.12.
You should call func convertDurationUnitValueToOtherUnits(durationValue:durationUnit:smallestUnitDuration:) from the answer that I already mentioned here:
let secondsToConvert = 27005
let result: [Int] = convertDurationUnitValueToOtherUnits(
durationValue: Double(secondsToConvert),
durationUnit: .seconds,
smallestUnitDuration: .seconds
)
print("\(result[0]) hours, \(result[1]) minutes, \(result[2]) seconds") // 7 hours, 30 minutes, 5 seconds
Answer of #r3dm4n was great. However, I needed also hour in it. Just in case someone else needed too here it is:
func formatSecondsToString(_ seconds: TimeInterval) -> String {
if seconds.isNaN {
return "00:00:00"
}
let sec = Int(seconds.truncatingRemainder(dividingBy: 60))
let min = Int(seconds.truncatingRemainder(dividingBy: 3600) / 60)
let hour = Int(seconds / 3600)
return String(format: "%02d:%02d:%02d", hour, min, sec)
}
Swift 5 & String Response, In presentable format
public static func secondsToHoursMinutesSecondsStr (seconds : Int) -> String {
let (hours, minutes, seconds) = secondsToHoursMinutesSeconds(seconds: seconds);
var str = hours > 0 ? "\(hours) h" : ""
str = minutes > 0 ? str + " \(minutes) min" : str
str = seconds > 0 ? str + " \(seconds) sec" : str
return str
}
public static func secondsToHoursMinutesSeconds (seconds : Int) -> (Int, Int, Int) {
return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)
}
Usage:
print(secondsToHoursMinutesSecondsStr(seconds: 20000)) // Result = "5 h 33 min 20 sec"
According to GoZoner answer I have wrote an Extension to get the time formatted according to the hours, minute, and seconds:
extension Double {
func secondsToHoursMinutesSeconds () -> (Int?, Int?, Int?) {
let hrs = self / 3600
let mins = (self.truncatingRemainder(dividingBy: 3600)) / 60
let seconds = (self.truncatingRemainder(dividingBy:3600)).truncatingRemainder(dividingBy:60)
return (Int(hrs) > 0 ? Int(hrs) : nil , Int(mins) > 0 ? Int(mins) : nil, Int(seconds) > 0 ? Int(seconds) : nil)
}
func printSecondsToHoursMinutesSeconds () -> String {
let time = self.secondsToHoursMinutesSeconds()
switch time {
case (nil, let x? , let y?):
return "\(x) min \(y) sec"
case (nil, let x?, nil):
return "\(x) min"
case (let x?, nil, nil):
return "\(x) hr"
case (nil, nil, let x?):
return "\(x) sec"
case (let x?, nil, let z?):
return "\(x) hr \(z) sec"
case (let x?, let y?, nil):
return "\(x) hr \(y) min"
case (let x?, let y?, let z?):
return "\(x) hr \(y) min \(z) sec"
default:
return "n/a"
}
}
}
let tmp = 3213123.printSecondsToHoursMinutesSeconds() // "892 hr 32 min 3 sec"
Here is what I use for my Music Player in Swift 4+. I am converting seconds Int to readable String format
extension Int {
var toAudioString: String {
let h = self / 3600
let m = (self % 3600) / 60
let s = (self % 3600) % 60
return h > 0 ? String(format: "%1d:%02d:%02d", h, m, s) : String(format: "%1d:%02d", m, s)
}
}
Use like this:
print(7903.toAudioString)
Output: 2:11:43
Latest Code: XCode 10.4 Swift 5
extension Int {
func timeDisplay() -> String {
return "\(self / 3600):\((self % 3600) / 60):\((self % 3600) % 60)"
}
}
From #Gamec answer
typealias CallDuration = Int
extension CallDuration {
func formatedString() -> String? {
let hours = self / 3600
let minutes = (self / 60) % 60
let seconds = self % 60
if hours > 0 { return String(format: "%0.2d:%0.2d:%0.2d", hours, minutes, seconds) }
return String(format: "%0.2d:%0.2d", minutes, seconds)
}
}
Usage:
let duration: CallDuration = 3000
duration.formatedString() // 50 minute
The simplest way imho:
let hours = time / 3600
let minutes = (time / 60) % 60
let seconds = time % 60
return String(format: "%0.2d:%0.2d:%0.2d", hours, minutes, seconds)
NSTimeInterval is Double do do it with extension. Example:
extension Double {
var formattedTime: String {
var formattedTime = "0:00"
if self > 0 {
let hours = Int(self / 3600)
let minutes = Int(truncatingRemainder(dividingBy: 3600) / 60)
formattedTime = String(hours) + ":" + (minutes < 10 ? "0" + String(minutes) : String(minutes))
}
return formattedTime
}
}
convert a number into time as a string
func convertToHMS(number: Int) -> String {
let hour = number / 3600;
let minute = (number % 3600) / 60;
let second = (number % 3600) % 60 ;
var h = String(hour);
var m = String(minute);
var s = String(second);
if h.count == 1{
h = "0\(hour)";
}
if m.count == 1{
m = "0\(minute)";
}
if s.count == 1{
s = "0\(second)";
}
return "\(h):\(m):\(s)"
}
print(convertToHMS(number:3900))
I went ahead and created a closure for this (in Swift 3).
let (m, s) = { (secs: Int) -> (Int, Int) in
return ((secs % 3600) / 60, (secs % 3600) % 60) }(299)
This will give m = 4 and s = 59. So you can format that as you wish. You may of course want to add hours as well, if not more information.
Swift 4 I'm using this extension
extension Double {
func stringFromInterval() -> String {
let timeInterval = Int(self)
let millisecondsInt = Int((self.truncatingRemainder(dividingBy: 1)) * 1000)
let secondsInt = timeInterval % 60
let minutesInt = (timeInterval / 60) % 60
let hoursInt = (timeInterval / 3600) % 24
let daysInt = timeInterval / 86400
let milliseconds = "\(millisecondsInt)ms"
let seconds = "\(secondsInt)s" + " " + milliseconds
let minutes = "\(minutesInt)m" + " " + seconds
let hours = "\(hoursInt)h" + " " + minutes
let days = "\(daysInt)d" + " " + hours
if daysInt > 0 { return days }
if hoursInt > 0 { return hours }
if minutesInt > 0 { return minutes }
if secondsInt > 0 { return seconds }
if millisecondsInt > 0 { return milliseconds }
return ""
}
}
useage
// assume myTimeInterval = 96460.397
myTimeInteval.stringFromInterval() // 1d 2h 47m 40s 397ms
neek's answer isn't correct.
here's the correct version
func seconds2Timestamp(intSeconds:Int)->String {
let mins:Int = (intSeconds/60)%60
let hours:Int = intSeconds/3600
let secs:Int = intSeconds%60
let strTimestamp:String = ((hours<10) ? "0" : "") + String(hours) + ":" + ((mins<10) ? "0" : "") + String(mins) + ":" + ((secs<10) ? "0" : "") + String(secs)
return strTimestamp
}
Another way would be convert seconds to date and take the components i.e seconds, minutes and hour from date itself.
This solution has limitation only till 23:59:59

Resources