mmoore410
mmoore410

Reputation: 427

Timer fires twice then doesn't reset

I've got a countdown timer that counts down and executes the code twice but then it doesn't reset, instead it continues counting in negative numbers. Can someone tell me why?

var didCount = 4

func startDelayTimer() {
    delayTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
        self.startDelayCount()
    })
}

func startDelayCount() {
    delayTime -= 1
    timeLbl.text = String(delayTime)
    if delayTime <= 3 {
        soundPLayer.play()
    }
    if delayTime == 0 {
        delayTimer.invalidate()
        doSomething()
    }
}

func doSomething() {
    doCount += 1
    if doCount < didCount {
        startDelayTimer()
    }
    else {
        print("done")
    }
}

Upvotes: 0

Views: 291

Answers (1)

Alexander
Alexander

Reputation: 63272

The direct issue is that you reset the timer without remebering to reset delayTime.

But I think there's also an architectural issue, in that you have a murky mix of responsibilities (managing a timer, updating a label, and playing sounds). I'd suggest you extract the timer responsibilities elsewhere.

Perhaps something along these lines:

/// A timer which counts from `initialCount` down to 0, firing the didFire callback on every count
/// After each full countdown, it repeats itself until the repeatLimit is reached.
class RepeatingCountDownTimer {
    typealias FiredCallback: () -> Void
    typealias FinishedCallback: () -> Void

    private var initialCount: Int
    private var currentCount: Int // Renamed from old "delayTime"
    private var repeatCount = 0 // Renamed from old "doCount"
    private let repeatLimit: Int // Renamed from old "didCount"

    private var timer: Timer?

    private let didFire: FiredCallback
    private let didFinish: FinishedCallback

    init(
        countDownFrom initialCount: Int,
        repeatLimit: Int,
        didFire: @escaping FiredCallback,
        didFinish: @escaping FinishedCallback
    ) {
        self.initialCount = initialCount
        self.currentCount = initialCount
        self.repeatLimit = repeatLimit

        self.didFire = didFire
        self.didFinish = didFinish
    }

    public func start() {
        self.currentCount = self.initialCount
        self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] in
            self?.fire()
        })
    }

    private func fire() {
        currentCount -= 1

        self.didFire(currentCount)

        if currentCount == 0 {
            repeat()
        }
    }

    private func repeat() {
        repeatCount += 1

        if repeatCount < repeatLimit {
            self.timer?.invalidate()
            start()
        } else {
            finished()
        }
    }

    private func finished() {
        self.timer?.invalidate()
        self.timer = nil
        self.didFinish()
    }
}

That's just rough psuedo-code, which will certainly need tweaking. But the idea is to separate timer and state management from the other things you need to do. This should make it easier to debug/develop/test this code, replacing useless names like doSomething with more concretely named events.

The usage might look something like:

let countDownTimer = RepeatingCountDownTimer(
    countDownFrom: 4,
    repeatLimit: 4,
    didFire: { count in
        timeLbl.text = String(count)
        soundPlayer.play()
    },
    didFinish: { print("done") }
)

countDownTimer.start()

Upvotes: 1

Related Questions