如何制作秒表计时器

How to make a stopwatch timer

出于某种原因,秒表看起来还不错,但与实际交易相比,它走得太快了。我不明白我应该做什么。是我应该改变的时间间隔吗?我希望它显示毫秒、秒和分钟,但我尝试的一切只会让情况变得更糟。谢谢

import UIKit
class ViewController: UIViewController {

    // Outlets
    @IBOutlet weak var minutes: UILabel!
    @IBOutlet weak var seconds: UILabel!
    @IBOutlet weak var milliSecondsLabel: UILabel!

    @IBOutlet weak var resetButton: UIButton!
    @IBOutlet weak var startButton: UIButton!
    @IBOutlet weak var pauseButton: UIButton!

    // Variables
    var timer = Timer()
    var time: Int = 0
    var running: Bool = false

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

    }

    @IBAction func resetTimer(_ sender: Any) {
        timer.invalidate()
        time = 0
        updateUI()
        running = false
    }

    @IBAction func startTimer(_ sender: Any) {
        if running {
            return
        } else {
            timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(timerDidEnd), userInfo: nil, repeats: true)
            running = true
        }

    }

    @IBAction func pauseTimer(_ sender: Any) {
        timer.invalidate()
        running = false
    }


    func roundButtons() {
        resetButton.layer.cornerRadius = resetButton.frame.height / 2
        startButton.layer.cornerRadius = startButton.frame.height / 2
        pauseButton.layer.cornerRadius = pauseButton.frame.height / 2
    }

    @objc func timerDidEnd() {
        time += 1
        updateUI()
    }

    func updateUI() {

        var min: Int
        var sec: Int
        var mil: Int

        min = time / (60*60)
        sec = (time/60)%60
        mil = time & 60

        minutes.text = String(min)
        seconds.text = String(sec)
        milliSecondsLabel.text = String(mil)
    }
}

我建议两件事:

  1. 不要自己数时间。捕获开始时间并从中计算经过的时间。您可以使用 Date 方法 timeIntervalSince, or, because that's not guaranteed to return monotonically increasing values, use CACurrentMediaTime,如下所示。

  2. 与其使用每秒任意更新 100 次的计时器,不如使用 CADisplayLink,它针对设备屏幕刷新率进行了最佳计时。

例如:

class ViewController: UIViewController {

    // Outlets
    @IBOutlet weak var minutesLabel: UILabel!
    @IBOutlet weak var secondsLabel: UILabel!
    @IBOutlet weak var milliSecondsLabel: UILabel!

    @IBOutlet weak var resetButton: UIButton!
    @IBOutlet weak var startButton: UIButton!
    @IBOutlet weak var pauseButton: UIButton!

    // Variables
    private weak var displayLink: CADisplayLink?
    private var startTime: CFTimeInterval?
    private var elapsed: CFTimeInterval = 0
    private var priorElapsed: CFTimeInterval = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        setFonts()
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        roundButtons()
    }

    @IBAction func resetTimer(_ sender: Any) {
        stopDisplayLink()
        elapsed = 0
        priorElapsed = 0
        updateUI()
    }

    @IBAction func startTimer(_ sender: Any) {
        if displayLink == nil {
            startDisplayLink()
        }
    }

    @IBAction func pauseTimer(_ sender: Any) {
        priorElapsed += elapsed
        elapsed = 0
        displayLink?.invalidate()
    }
}

private extension ViewController {
    func startDisplayLink() {
        startTime = CACurrentMediaTime()
        let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
        displayLink.add(to: .main, forMode: .common)
        self.displayLink = displayLink
    }

    func stopDisplayLink() {
        displayLink?.invalidate()
    }

    @objc func handleDisplayLink(_ displayLink: CADisplayLink) {
        guard let startTime = startTime else { return }
        elapsed = CACurrentMediaTime() - startTime
        updateUI()
    }

    func updateUI() {
        let totalElapsed = elapsed + priorElapsed

        let hundredths = Int((totalElapsed * 100).rounded())
        let (minutes, hundredthsOfSeconds) = hundredths.quotientAndRemainder(dividingBy: 60 * 100)
        let (seconds, milliseconds) = hundredthsOfSeconds.quotientAndRemainder(dividingBy: 100)

        minutesLabel.text = String(minutes)
        secondsLabel.text = String(format: "%02d", seconds)
        milliSecondsLabel.text = String(format: "%02d", milliseconds)
    }

    func roundButtons() {
        resetButton.layer.cornerRadius = resetButton.bounds.height / 2
        startButton.layer.cornerRadius = startButton.bounds.height / 2
        pauseButton.layer.cornerRadius = pauseButton.bounds.height / 2
    }

    func setFonts() {
        minutesLabel.font = UIFont.monospacedDigitSystemFont(ofSize: minutesLabel.font.pointSize, weight: .regular)
        secondsLabel.font = UIFont.monospacedDigitSystemFont(ofSize: secondsLabel.font.pointSize, weight: .regular)
        milliSecondsLabel.font = UIFont.monospacedDigitSystemFont(ofSize: milliSecondsLabel.font.pointSize, weight: .regular)
    }
}

结果:


完全不相关,但任何依赖于视图大小的东西(例如圆角)确实属于 viewDidLayoutSubviews,而不属于 viewDidLoad