Search code examples
swiftintcountdowntimeruitextfielddelegate

format countdown from total seconds to minute and seconds


My swift code below lets the user enter an amount of minutes and then when it does it counts it down. However if the user enters 12:00 it counts down from 720. I want it to format it to count it down like 12:00, 11:59 etc. All my code is below no storyboard. Func timerAction is where all of this is happening.

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {
let MAX_LENGTH_PHONENUMBER = 2
let ACCEPTABLE_NUMBERS     = "0123456789"
var enterTime = UITextField()
var lblTime = UILabel()
var startBTN = UIButton()
var timer = Timer()
var counter = 0

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.

    [enterTime,lblTime,startBTN].forEach{
        $0.backgroundColor = .systemRed
        view.addSubview($0)
        $0.translatesAutoresizingMaskIntoConstraints = false
    }

    enterTime.frame = CGRect(x: view.center.x-115, y: view.center.y-200, width: 60, height: 50)
    lblTime.frame = CGRect(x: view.center.x-115, y: view.center.y, width: 60, height: 50)
    startBTN.frame = CGRect(x: view.center.x-115, y: view.center.y+200, width: 60, height: 50)

    startBTN.addTarget(self, action: #selector(startHit), for: .touchDown)

    enterTime.placeholder = String("MM:SS")

    enterTime.delegate = self
    enterTime.addTarget(self, action: #selector(self.textFieldDidChange), for: UIControl.Event.editingChanged)
}



@objc func startHit() {
    timer.invalidate()
    counter = convertToSeconds(from: enterTime.text!)
    lblTime.text = String(counter)
    timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}

@objc func timerAction() {
    counter -= 1
    lblTime.text = String(counter)
    if ( counter == 0 ) {
        timer.invalidate()
    }
}

func convertToSeconds(from timeString: String) -> Int {
    let components = timeString.components(separatedBy: ":")
    if components.count == 2 {
        let minutes = Int(components[0]) ?? 0
        let seconds = Int(components[1]) ?? 0
        return (minutes * 60) + seconds
    } else if components.count == 3 {
        let hours = Int(components[0]) ?? 0
        let minutes = Int(components[1]) ?? 0
        let seconds = Int(components[2]) ?? 0
        return (hours * 60 * 60) + (minutes * 60) + seconds
    } else {
        return 0
    }
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

    let newLength: Int = textField.text!.count + string.count - range.length
    let numberOnly = NSCharacterSet.init(charactersIn: ACCEPTABLE_NUMBERS).inverted
    let strValid = string.rangeOfCharacter(from: numberOnly) == nil
    return (strValid && (newLength <= MAX_LENGTH_PHONENUMBER))
}



@objc func textFieldDidChange(_ textField: UITextField) {
    if  textField.text!.count == 2  {
        textField.text = textField.text! + ":00"
    }
}
}

Solution

  • Right now, you are just converting the integer representing the number of seconds directly to a string, in these two places:

    @objc func startHit() {
        timer.invalidate()
        counter = convertToSeconds(from: enterTime.text!)
        lblTime.text = String(counter) <--------
        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
    }
    
    @objc func timerAction() {
        counter -= 1
        lblTime.text = String(counter) <--------
        if ( counter == 0 ) {
            timer.invalidate()
        }
    }
    

    You need to change these two places to use a more complicated formatting logic, to turn something like 720 to 12:00:

    @objc func startHit() {
        timer.invalidate()
        counter = convertToSeconds(from: enterTime.text!)
        lblTime.text = formatSeconds(counter)
        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
    }
    
    @objc func timerAction() {
        counter -= 1
        lblTime.text = formatSeconds(counter)
        if ( counter == 0 ) {
            timer.invalidate()
        }
    }
    

    And here is the implementation of formatSeconds, which makes use of a DateComponentsFormatter. This is a formatter that is great for formatting amounts of time like this.

    func formatSeconds(_ totalSeconds: Int) -> String {
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .positional
        formatter.allowedUnits = [.minute, .second]
        formatter.zeroFormattingBehavior = .pad
        return formatter.string(from: TimeInterval(totalSeconds))!
    }