0

Need help to make a countdown timer that waits until it is invalidated to proceed. It also should not be blocking main thread. Any tips?

private var currentCountdownSeconds = 3
private var countdownTimer = Timer()

private func performTimer() {

    if secondsToCountDown != 0 {
        print(secondsToCountDown)
        countdownTimer = Timer(timeInterval: 1, target: self, selector: #selector(handleCountdown), userInfo: nil, repeats: true)
    }

}

@objc private func handleCountdown() {
    previewView.countdownLabel.text = "\(currentCountdownSeconds)"
    currentCountdownSeconds -= 1
    print(secondsToCountDown)
    if secondsToCountDown == 0 {
        countdownTimer.invalidate()
    }
}

public func toggleMovieRecording() {
    handleTimer()
    videoCaptureLogic()
}

public func toggleCapturePhoto() {
    handleTimer()
    videoCaptureLogic()
}
  • Well, what's the problem in your code? – Ahmad F Jul 27 '18 at 10:56
  • why did you not just give `countDownTimer = Timer(timeInterval: currentCountdownSeconds, target: self, selector: #selector(handleCountdown), userInfo: nil, repeats: true)` and just invalidate it once the selector was called? – Rakesha Shastri Jul 27 '18 at 11:03
  • @AhmadF Code performs without errors, however, I don't know how to make code wait until timer to invalidated to proceed to another line of code. – Miras Karazhigitov Jul 27 '18 at 11:03
  • @RakeshaShastri , I believe your solution would make selector be called once every 3 seconds. I need to update label every second to make it look like a countdown timer. – Miras Karazhigitov Jul 27 '18 at 11:05
  • Just put *another line of code* into the `handleCountdown()` method after the line `countdownTimer.invalidate()` – vadian Jul 27 '18 at 11:12
  • Oh i missed that part of you actually wanting to show progress. _whoops_ – Rakesha Shastri Jul 27 '18 at 11:14
  • What is the error you get? – Rakesha Shastri Jul 27 '18 at 11:15
  • @vadian I thought about that, but is there a more elegant way? Your solution is the best so far, however, it will make code structure messy. – Miras Karazhigitov Jul 27 '18 at 11:18
  • @RakeshaShastri Code performs without errors, however, I don't know how to make code wait until timer to invalidated to proceed to another line of code (duplicate from above) – Miras Karazhigitov Jul 27 '18 at 11:19
  • You can’t make the code wait. You handle the countdown only in the selector for the timer which is the only flow possible. Just call the method after invalidating like how vadian said. Unless you need changes to reflect in multiple places. In which case you can use Notifications. – Rakesha Shastri Jul 27 '18 at 11:22
  • @RakeshaShastri , yea, I was going to point that out. I needed handleTimer in `capturePhoto` and `captureVideo`, and @vadian 's solution was not a panacea as I initially thought. – Miras Karazhigitov Jul 27 '18 at 11:50
  • You can use DispatchGroups. Make a new DispatchGroup, enter the group when the timer is started and leave the group when the timer is invalidated. – ndreisg Jul 27 '18 at 11:52

2 Answers2

0

You can use DispatchGroups. Enter the group when the timer is started and leave the group when the timer is invalidated.

private var currentCountdownSeconds = 3
private var countdownTimer = Timer()
private let timerDispatchGroup = DispatchGroup() // Init DispatchGroup

private func performTimer() {

    if secondsToCountDown != 0 {
        print(secondsToCountDown)
        timerDispatchGroup.enter() // Enter DispatchGroup
        countdownTimer = Timer(timeInterval: 1, target: self, selector: #selector(handleCountdown), userInfo: nil, repeats: true)
    }

}

@objc private func handleCountdown() {
    previewView.countdownLabel.text = "\(currentCountdownSeconds)"
    currentCountdownSeconds -= 1
    print(secondsToCountDown)
    if secondsToCountDown == 0 {
        countdownTimer.invalidate()
        timerDispatchGroup.leave() // Leave DispatchGroup
    }
}

public func toggleMovieRecording() {
    performTimer() // I guess it should be performTimer instead of handleTimer?
    timerDispatchGroup.notify(queue: .main) { videoCaptureLogic() } // Closure 
}

public func toggleCapturePhoto() {
    performTimer()
    timerDispatchGroup.notify(queue: .main) { videoCaptureLogic() }
}
ndreisg
  • 1,119
  • 13
  • 33
  • interesting, I am not familiar DispatchGroup. Let me explore. RIght now your solution gives me (`Cannot invoke 'notify' with an argument list of type '(() -> ())'`) error. – Miras Karazhigitov Jul 27 '18 at 12:33
0

Try this one if you have to use the countdown in multiple places.

private var currentCountdownSeconds = 3
private var countdownTimer = Timer()

private func performTimer() {

    if secondsToCountDown != 0 {
        print(secondsToCountDown)
        countdownTimer = Timer(timeInterval: 1, target: self, selector: #selector(handleCountdown), userInfo: nil, repeats: true)
    }

}

@objc private func handleCountdown() {
    previewView.countdownLabel.text = "\(currentCountdownSeconds)"
    currentCountdownSeconds -= 1
    print(secondsToCountDown)
    if secondsToCountDown == 0 {
        countdownTimer.invalidate()
        NotificationCenter.default.post(notification: NSNotification.Name.init("TimeComplete")
    }
}

Now implement the observers for the notification in whichever classes you need it in. The respective selectors will handle the event when countdown completes and the notification is posted.

class Something {

    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(timerCompleteAction), name: NSNotification.Name.init("TimerComplete"), object: nil)
    }

    @objc func timerCompleteAction() {
          //Do necessary actions
    }
}

Something can be your class which has video capture in which case write that code in the timerCompleteAction. Then again in the class where you have the audio capture, add the same observer and add the selector method and do the audio capture actions.

Rakesha Shastri
  • 11,053
  • 3
  • 37
  • 50