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()
}
}
You can use a closure:
class MyTimer: NSObject {
var startTime: NSTimeInterval! = NSTimeInterval()
var time: Double! = 4
var timer: NSTimer! = NSTimer()
var timerEndedCallback: (() -> Void)!
var timerInProgressCallback: ((elapsedTime: Double) -> Void)!
func startTimer(timerEnded: () -> Void, timerInProgress: ((elapsedTime: Double) -> Void)!) {
if !timer.valid {
let aSelector : Selector = "updateTime"
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: aSelector, userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate()
timerEndedCallback = timerEnded
timerInProgressCallback = timerInProgress
}
}
func updateTime() {
var currentTime = NSDate.timeIntervalSinceReferenceDate()
var elapsedTime = currentTime - startTime
var seconds = time - elapsedTime
if seconds > 0 {
elapsedTime -= NSTimeInterval(seconds)
timerInProgressCallback(elapsedTime: elapsedTime)
} else {
timer.invalidate()
timerEndedCallback()
}
}
}
Usage, in the class you want to use your generic timer:
MyTimer().startTimer({ () -> Void in
timerLabel.fadeOut()
}, timerInProgress: { (elapsedTime) -> Void in
timerLabel.text = "\(Int(elapsedTime))"
})
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()
}
}
}
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)
}
)
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.
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()
Related
I'm trying to get output like so:
1 (then a one second delay)
Hello
2 (then a one second delay)
Hello
3 (then a one second delay)
Hello
But instead I get
1
2
3 (then a one second delay)
Hello
Hello
Hello
Here's my for loop invoking the NSTimer
var timer = NSTimer()
for i in 1...3 {
print("\(i)");
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(MainVPScreenViewController.printTest), userInfo: nil, repeats: false)
}
And here's the selector method:
func printTest() {
print("Hello")
}
Thanks in advance for any help you can provide
Try this solution without NSTimer:
var i = 1
func printHello() {
print(i)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
print("Hello")
i +=1
if i <= 3 {
printHello()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
printHello()
}
I need 2 NSTimers to do this, this is my approach
class ViewController: UIViewController {
var i = 1
override func viewDidLoad() {
super.viewDidLoad()
beginPrinting()
}
func beginPrinting() {
var timer2 = NSTimer()
if(i <= 100)
{
timer2 = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(self.printWithDelay), userInfo: nil, repeats: false)
}
}
func printWithDelay()
{
var timer = NSTimer()
print("\(i)");
i += 1
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(self.printTest), userInfo: nil, repeats: false)
}
func printTest() {
print("Hello")
beginPrinting()
}
}
Hope this helps you
Use timer with repeat to true. So in your view controller would be like this
var timer = NSTimer()
var counter = 0
var max = 10
let delay = 1 // in second
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer.scheduledTimerWithTimeInterval(delay, target: self,
selector: #selector(self.printTest), userInfo: nil, repeats: true)
}
func printTest() {
counter += 1
print(counter)
print(hello)
if counter == maxNumber {
timer.invalidate()
}
}
This does it with repeat false, and is set up to be in a playground:
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
#objc class Foo: NSObject {
static var timer = NSTimer()
var i:Int
override init() {
Foo.timer = NSTimer()
i = 1
}
func schedule() {
print("\n\(i)");
i += 1
Foo.timer = NSTimer.scheduledTimerWithTimeInterval(1.0,
target: self,
selector: #selector(printTest),
userInfo: nil,
repeats: false)
}
#objc func printTest() {
print("Hello")
if i < 5 {
schedule()
}
}
}
let bar = Foo()
bar.schedule()
I have a TimerManager class that I would like to access in multiple ViewControllers but I can't figure out a good way to do it. My code is as follows:
class TimerManager {
private var timer: NSTimer
private var timeRemaining: Int
init(initialTime: Int) {
self.timer = NSTimer()
self.timeRemaining = initialTime
}
func startTimer() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(TimerManager.update), userInfo: nil, repeats: true)
}
func endTimer() {
self.timer.invalidate()
}
func getTimeRemaining() -> Int {
return self.timeRemaining
}
#objc func update() {
if self.timeRemaining > 0 {
self.timeRemaining = self.timeRemaining - 1
}
else {
endTimer()
}
}
}
In my ViewController I would like to be able to access my update() function to update a timer (which is a UILabel) on my actual page, but since my startTimer() function calls it every second, I don't know how to access update() every time it is called. I briefly looked into protocols but I'm not really sure how they work or if that would be useful in my case.
Any help would be appreciated!
As #sschale suggested, you can do this by using a singleton to ensure that you will be accessing the same instance anywhere in your code. To do this, you need to set the init to private and provide a static member variable to access your single instance.
class TimerManager
{
static let sharedInstance = TimerManager()
private var timer: NSTimer
private var timeRemaining: Int
private init()
{
let initialTime = 1
self.timer = NSTimer()
self.timeRemaining = initialTime
}
private init(initialTime: Int)
{
self.timer = NSTimer()
self.timeRemaining = initialTime
}
...
}
Then in your ViewControllers you can just call it like this:
TimerManager.sharedInstance.startTimer()
class TimerManager {
private var timer: NSTimer
private var timeRemaining: Int
private var intervalBlock: (TimerManager -> ())?
init(initialTime: Int) {
self.timer = NSTimer()
self.timeRemaining = initialTime
}
func startTimer(intervalBlock: (TimerManager -> ())? = nil) {
self.intervalBlock = self
self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(TimerManager.update), userInfo: nil, repeats: true)
}
func endTimer() {
self.intervalBlock = nil
self.timer.invalidate()
}
func getTimeRemaining() -> Int {
return self.timeRemaining
}
#objc func update() {
if self.timeRemaining > 0 {
self.timeRemaining = self.timeRemaining - 1
intervalBlock()
}
else {
intervalBlock()
endTimer()
}
}
}
Below is one of the best implementations of Timer on the background queue I found from this article
class RepeatingTimer {
let timeInterval: TimeInterval
init(timeInterval: TimeInterval) {
self.timeInterval = timeInterval
}
private lazy var timer: DispatchSourceTimer = {
let t = DispatchSource.makeTimerSource()
t.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval)
t.setEventHandler(handler: { [weak self] in
self?.eventHandler?()
})
return t
}()
var eventHandler: (() -> Void)?
private enum State {
case suspended
case resumed
}
private var state: State = .suspended
deinit {
timer.setEventHandler {}
timer.cancel()
resume()
eventHandler = nil
}
func resume() {
if state == .resumed {
return
}
state = .resumed
timer.resume()
}
func suspend() {
if state == .suspended {
return
}
state = .suspended
timer.suspend()
}
}
Usage: -
In any of your ViewControllers
For example: -
class MyViewController: UIViewController {
// MARK: - Properties
var timer: RepeatingTimer!
// MARK: - ViewController LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
timer = RepeatingTimer(timeInterval: 1)
timer.eventHandler = {
print("Timer called")
}
}
I need launch the timer few times in sequence (one after another). And of course I need update Label with timer results.
For example, I have two periods (50 sec and 10 sec) and I need to make a series of periods: 50-10-50-10-50-10.
How can I do it?
import UIKit
class StartTimerViewController: UIViewController {
let firstPeriodTime = 50
let secondPeriodTime = 10
var currentPeriodTime: Int!
let repetitionTime = 3
var timer: NSTimer!
var timeCount = 0
#IBOutlet weak var timerLabel: UILabel!
// MARK: - IBAction method implementation
#IBAction func start(sender: AnyObject) {
// I know it's wrong... This is my question!!!!!
var i = 1
while i <= repetitionTime {
currentPeriodTime = firstPeriodTime
startTimer()
currentPeriodTime = secondPeriodTime
startTimer()
i = i + 1
}
}
// MARK: - Timer method implementation
func startTimer() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target:self, selector: "updateCounter", userInfo: nil, repeats: true)
print("timer start")
}
func updateCounter() {
if timeCount < currentPeriodTime {
timeCount++
let currentTime = Double(currentPeriodTime - timeCount)
timerLabel.text = timeString(currentTime)
}
else {
timer.invalidate()
timeCount = 0
}
}
func timeString(time:NSTimeInterval) -> String {
let minutes = Int(time) / 60
let seconds = time - Double(minutes) * 60
return String(format:"%02i:%02i",minutes,Int(seconds))
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Please use the code below
#IBOutlet weak var _lblTimer: UILabel!
var timer = NSTimer()
var intValue = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "update50:", userInfo: nil, repeats: true)
}
func update50(timer : NSTimer){
intValue += 1
_lblTimer.text = intValue.description
if(intValue == 50){
intValue = 0
timer.invalidate()
self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "update10:", userInfo: nil, repeats: true)
}
}
func update10(timer : NSTimer){
intValue += 1
_lblTimer.text = intValue.description
if(intValue == 10){
intValue = 0
timer.invalidate()
self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "update50:", userInfo: nil, repeats: true)
}
}
I'm trying to make a laundry timer app in Swift where the washer and drying will have different starting times, counting down to 0.
func updateTime() {
...
var elapsedTime: NSTimeInterval = operationDuration - (currentTime - startTime)
...
}
In the var elapsedTime I have operationDuration (how long the washer or dryer will take) and later on in the #IBAction func for pressing the "washer" button I have
let operationDuration == 1800
However I am getting an error the the updateTime func that
'operationDuration' is not defined.
How can I do this? Thanks in advance.
edit:
Here is my washerButtonPress code:
#IBAction func washerButtonPress(sender: AnyObject) {
// TODO: start 30 minute countdown
if !timer.valid {
var operationDuration = 1800
let aSelector:Selector = "updateTime"
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate()
}
And how I'm trying to call it in my func updateTime()
var elapsedTime: NSTimeInterval = washerButtonPress.operationDuration - (currentTime - startTime)
and it returns '(AnyObject) -> ()' does not have a member named 'operationDuration'
I apologize for not knowing much but I'm pretty new to this
If this is merely a constant, you can declare it as a global variable.
let kOperationDuration = 1800
class Washer {
...
#IBAction func washerButtonPress(sender: AnyObject) {
if !timer.valid {
let aSelector:Selector = "updateTime"
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate()
}
}
...
}
Then in the Dryer class you can reference the constant.
class Dryer {
...
func updateTime() {
...
var elapsedTime: NSTimeInterval = kOperationDuration - (currentTime - startTime)
...
}
...
}
My game is supposed to be stopped after 60s. But nothing happens with my timer code.
var timer = NSTimer()
var counter:Int = 60
var labelCounter:SKLabelNode = SKLabelNode()
Here is the code in my GameScene class :
func startTimer()
{
timer = NSTimer.scheduledTimerWithTimeInterval(1.0
, target: self, selector: Selector("updateTimer:"), userInfo: nil, repeats: true)
}
func updateTimer(dt:NSTimer)
{
counter--
if counter == 0 {
timer.invalidate()
removeCountDownTimerView()
} else{
labelCounter.text = "\(counter)"
}
}
func removeCountDownTimerView()
{
scene.view.paused = true
}
thank you for your insight :-)
Try something like this....
override func viewDidLoad() {
super.viewDidLoad()
//calling the wait function
self.callForWait()
}
func callForWait(){
//setting the delay time 60secs.
let delay = 60 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
//call the method which have the steps after delay.
self.stepsAfterDelay()
}
}
func stepsAfterDelay(){
//your code after delay takes place here...
}
I had this in a game. Hope it helps:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
startGame()
}
var startTime = NSTimeInterval()
var timer = NSTimer()
var gameTime:Double = 10
func startGame() {
let aSelector : Selector = "updateTime"
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: aSelector, userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate()
}
func updateTime() {
var currentTime = NSDate.timeIntervalSinceReferenceDate()
var elapsedTime = currentTime - startTime
var seconds = gameTime-elapsedTime
if seconds > 0 {
elapsedTime -= NSTimeInterval(seconds)
println("\(Int(seconds))")
} else {
timer.invalidate()
}
}
}