'How to make a timer in iOS Swift in a separate class?

I've built a CountdownTimer which i would like to refactor into a separate class so i can reuse it in the MainViewController. How would i go about doing that?

This is my code:

var startTime = NSTimeInterval()
var time:Double = 4
var timer = NSTimer()

/* Outlets */
@IBOutlet weak var timerLabel: UILabel!

 /* CountdownTimer function */
func updateTime() {

    var currentTime = NSDate.timeIntervalSinceReferenceDate()
    var elapsedTime = currentTime - startTime
    var seconds = time - elapsedTime

    if seconds > 0 {
        elapsedTime -= NSTimeInterval(seconds)
        timerLabel.text = "\(Int(seconds))"
    } else {
        timer.invalidate()
        timerLabel.fadeOut()
    }
}

func startTimer () {
    if !timer.valid {
        let aSelector : Selector = "updateTime"
        timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: aSelector, userInfo: nil, repeats: true)
        startTime = NSDate.timeIntervalSinceReferenceDate()
    }
}


Solution 1:[1]

swift 4 version of tbaranes's answer:

class MyTimer: NSObject {

var startTime: TimeInterval! = TimeInterval()
var time: Double! = 4
var timer: Timer! = Timer()
var timerEndedCallback: (() -> Void)!
var timerInProgressCallback: ((_ elapsedTime: Double) -> Void)!

func startTimer(timerEnded: @escaping () -> Void, timerInProgress: ((_ elapsedTime: Double) -> Void)!) {
    if !timer.isValid {
        let aSelector : Selector = #selector(MyTimer.updateTime)
        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: aSelector, userInfo: nil, repeats: true)
        startTime = NSDate.timeIntervalSinceReferenceDate
        timerEndedCallback = timerEnded
        timerInProgressCallback = timerInProgress
    }
}

@objc func updateTime() {
    var currentTime = NSDate.timeIntervalSinceReferenceDate
    var elapsedTime = currentTime - startTime
    var seconds = time - elapsedTime

    if seconds > 0 {
        elapsedTime -= TimeInterval(seconds)
        timerInProgressCallback(elapsedTime)
    } else {
        timer.invalidate()
        timerEndedCallback()
    }
}
}

Solution 2:[2]

Based on @tbaranes's answer and because the edit queue is full, here's an updated version that works on SWIFT 5 with some tweaks

protocol CountdownTimerProtocol {
    func stopCountdown()
    func startCountdown(totalTime: Int, timerEnded: @escaping () -> Void, timerInProgress: @escaping (Int) -> Void)
}

class CountdownTimer: NSObject, CountdownTimerProtocol {
    private var timer: Timer?
    private var timeRemaining = 0
    var timerEndedCallback: (() -> Void)?
    var timerInProgressCallback: ((Int) -> Void)?
        
    deinit {
        stopCountdown()
    }
    
    func stopCountdown() {
        timer?.invalidate()
    }
    
    func startCountdown(totalTime: Int, timerEnded: @escaping () -> Void, timerInProgress: @escaping (Int) -> Void) {
        timeRemaining = totalTime
        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(step), userInfo: nil, repeats: true)
        timerEndedCallback = timerEnded
        timerInProgressCallback = timerInProgress
    }
    
    @objc func step() {
        if timeRemaining > 0 {
            timeRemaining -= 1
            timerInProgressCallback?(timeRemaining)
        } else {
            stopCountdown()
            timerEndedCallback?()
        }
    }
}

How to call it, totalTime is measured in seconds, also remember to invalidate() the timer on deinit {} of this class ??

   CountdownTimer().startCountdown(
        totalTime: 30,
        timerEnded: {
            print("Countdown is over")
        }, timerInProgress: { elapsedTime in
            print(elapsedTime)
        }
    )

Solution 3:[3]

To use your countdownTimer in a separate class, create a new class and implement the class in your MainViewController and then access the methods.

Alternatively just make your functions global.

Solution 4:[4]

XCode->File->New->Source->Cocoa Touch Class extends from NSObject

import UIKit
class TimerTest: NSObject {
    var myTimer:NSTimer?
    override init() {
        super.init()
        print("init worked")
        myTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "timerFuncTriggered:", userInfo: nil, repeats: true)
    }
    func timerFuncTriggered(timer:NSTimer) {
        print("timer started")
    }
}

you can use your timer class anytime like:

let myTimer = TimerTest()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Paul Lehn
Solution 2 Javier Heisecke
Solution 3 Wraithseeker
Solution 4 Özgür Ersil