timer doesn't work in cocoa/mac OS in Swift 4 - swift

I cant see why this time doesnt update. Been looking the entire day. I don't know if is something related to being my first MacOs project, and there's maybe something that's escaping me, but I'd love some help.
import Cocoa
class TextViewController: NSViewController {
#IBOutlet weak var text: NSScrollView!
#IBOutlet weak var dreadline: NSTextField!
var seconds: Int = 60
var timer: Timer?
var theWork = Dreadline(email: "", worktime: 0)
override func viewDidLoad() {
super.viewDidLoad()
print(seconds)
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
print(self.seconds)
self.updateTimer()
} // this is the timer that doesn't work no matter what I try :(
}
#objc func updateTimer() {
if seconds < 1 {
timer?.invalidate()
} else {
seconds -= 1 //This will decrement(count down)the seconds.
dreadline.stringValue = "Dreadline: " + timeString(time: TimeInterval(seconds)) //This will update the label.
}
}

A very common mistake: You are creating a local timer which is not the same as the declared property.
Replace
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
print(self.seconds)
self.updateTimer()
} // this is the timer that doesn't work no matter what I try :(
with
self.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
print(self.seconds)
self.updateTimer()
} // this is the timer that doesn't work no matter what I try :(
The self before timer is actually not mandatory.
And set the timer to nil after invalidation to avoid a retain cycle
if seconds < 1 {
timer?.invalidate()
timer = nil
}
On the other hand you can use the local timer by deleting the property and change the code to
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
print(self.seconds)
self.updateTimer(timer)
}
func updateTimer(_ timer : Timer) {
if seconds < 1 {
timer.invalidate()
} else {
seconds -= 1 //This will decrement(count down)the seconds.
dreadline.stringValue = "Dreadline: " + timeString(time: TimeInterval(seconds)) //This will update the label.
}
}

Related

Unable to stop timer through another viewcontroller, Swift

I developing a timer app for an apple watch.
I have two different Views at the moment. One with the actual timer (TimerController) and another with a pause-button (SwipeController).
I'm trying to stop/start the timer in the TimerController with the action from the button in the SwipeController.
Problem is that the timer stops, but the timer will not start again after hitting the button the second time.
If I press the button one time, the timer stops. If i press it again two times the timer will start again but will not stop when hitting the button again.
Any ideas of what the problem could be?
TimeController
import WatchKit
import Foundation
import UserNotifications
class TimerController: WKInterfaceController {
#IBOutlet weak var timerOutlet: WKInterfaceTimer! //
#IBOutlet weak var simple_timer_label: WKInterfaceLabel!
var myTimer : Timer?
var duration : TimeInterval = 1 //arbitrary number. 1 seconds
var isPaused = false //flag to determine if it is paused or not
var elapsedTime : TimeInterval = 0.0 //time that has passed between
var number_as_a_timer:Int = 0
var startTime = NSDate()
var dim_date = Date()
var current_minute: Int = 0
var current_hour: Int = 0
var curent_second: Int = 0
var seperate_is_paused_bool: Bool = false
override func awake(withContext context: Any?) {
super.awake(withContext: context)
start_timer()
}
func timeString(time:TimeInterval) -> String {
let hours: Int = Int(time) / 3600
let minutes: Int = Int(time) / 60 % 60
let seconds: Int = Int(time) % 60
let com = NSDateComponents()
com.minute = minutes
com.second = seconds
com.hour = hours
dim_date = NSCalendar.current.date(from: com as
DateComponents)!
self.timerOutlet.setDate(dim_date)
self.timerOutlet.start()
return String(format:"%02i:%02i:%02i", hours, minutes, seconds)
}
func start_timer() {
myTimer = Timer.scheduledTimer(timeInterval: duration, target:
self,selector: #selector(timerDone), userInfo: nil, repeats:
true)
}
#objc private func timerDone(){
//timer done counting down
if !isPaused {
number_as_a_timer += 1
let output:String = self.timeString(time:
TimeInterval(number_as_a_timer))
self.simple_timer_label.setText(output)
print(output)
}
}
override func willActivate() {
super.willActivate()
NotificationCenter.default.addObserver(self, selector:
#selector(stop_timer(notification:)), name: .stopTimer, object:
nil)
}
#objc func stop_timer(notification:NSNotification) {
// Timer is paused. so unpause it and resume countdown
if isPaused {
myTimer = Timer.scheduledTimer(timeInterval: 1,
target:self, selector: #selector(timerDone), userInfo: nil,
repeats: true)
self.isPaused = false
print("timer paused: resumming1")
} else {
isPaused = true
print("stoping timer")
//get how much time has passed before they paused it
let paused = NSDate()
elapsedTime += paused.timeIntervalSince(startTime as Date)
//stop watchkit timer on the screen
timerOutlet.stop()
//stop the ticking of the internal timer
myTimer!.invalidate()
}
}
}
extension Notification.Name {
static let stopTimer = Notification.Name("stopTimer")
}
SwipeController
import WatchKit
import Foundation
import UserNotifications
class SwipeController: WKInterfaceController {
//#IBOutlet weak var myTimer: WKInterfaceTimer!
var timer = TimerController()
var status: Bool = false
override func awake(withContext context: Any?) {
super.awake(withContext: context)
}
#IBAction func PauseButton() {
if timer.myTimer == nil {
print("timer is nil or invalidated")
print("Y: \(timer.isPaused)")
let userInfo = ["stop": true] as [String: Bool] // you
could also transfer data
NotificationCenter.default.post(name: .stopTimer, object:
nil, userInfo: userInfo)
} else {
print("empty block")
}
}
}
it looks like you aren't ever actually checking for you isPaused boolean to be true or false in your if statement when checking if your timer is paused.
if isPaused { <-----------
myTimer = Timer.scheduledTimer(timeInterval: 1,
target:self, selector: #selector(timerDone), userInfo: nil,
repeats: true)
self.isPaused = false
print("timer paused: resumming1")

Stopping timer at defined amount of time in Swift

I am trying to build a stopwatch which, for instance, will count to 3.0 seconds, stop, and then allow me to override the app's view with a new background/label. My issue is I cannot find a way for the timer to stop and pause on its own at 3 seconds - whenever I'd write the statement, it'd just continue counting and not do anything. Where would I put the statement in this code, and how would I write it?
import UIKit
class ViewController: UIViewController {
var time = 0.0
var timer = Timer()
#IBOutlet weak var lbl: UILabel!
#IBAction func start(_ sender: UIButton)
{
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(ViewController.action), userInfo: nil, repeats: false)
}
#IBAction func pause(_ sender: UIButton)
{
timer.invalidate()
}
#IBAction func reset(_ sender: UIButton)
{
timer.invalidate()
time = 0.0
lbl.text = ("0")
}
#objc func action()
{
time += 0.1
lbl.text = String(time)
}
}
Start by decoupling your expectations.
A "clock" is a container for the period of time from which it was started to now. Additionally, it could be "restarted", so it may need to know how long each previous run cycle was, this would then be added into the overall duration of the "clock"
The Timer is simply a way to run some code on periodical bases. Because a Timer only guarantees "at least" period, it should avoid been used for simple counter addition, as it can cause drift in your calculations (for a simple clock, it's probably not a big deal, but if you need any kind of precision, it's best to avoid it)
SimpleClock
import Foundation
public class SimpleClock {
internal var startedAt: Date? = nil
internal var totalRunningTime: TimeInterval = 0 // Used for pause/resume
var isRunning: Bool = false {
didSet {
if isRunning {
startedAt = Date()
} else {
totalRunningTime += currentCycleDuration
self.startedAt = nil
}
}
}
// This is the amount of time that this cycle has been running,
// that is, the amount of time since the clock was started to now.
// It does not include other cycles
internal var currentCycleDuration: TimeInterval {
guard let startedAt = startedAt else {
return 0
}
return Date().timeIntervalSince(startedAt)
}
func reset() {
isRunning = false
totalRunningTime = 0
}
// This is the "total" amount of time the clock has been allowed
// to run for, excluding periods when the clock was paused
var duration: TimeInterval {
return totalRunningTime + currentCycleDuration
}
}
Okay, this is pretty basic concept. It's just a container for recording when a "cycle" starts and stops and managing the "overall" duration (start/pause/resume cycles)
That's all fine and good, but what we really want is some way to determine if the period has "timeout" or not.
AlarmClock
import Foundation
class AlarmClock: SimpleClock {
var timeout: TimeInterval = 0
var hasExpired: Bool {
return duration >= timeout
}
var timeRemaining: TimeInterval {
return max(timeout - duration, 0)
}
}
All this does is add a concept of a "timeout" period and provides some additional functionality that allows use to easily determine if the clock has expired and the amount of time remaining
Example
Okay, that's all nice a good, but how does this work (and help us)
Okay, this is a really simple example. It has a label and two buttons. One button starts/pauses the clock and the other resets it.
The label displays both the running time and the remaining time of the alarm clock. If he clock expires, it will automatically be reset.
The class contains a Timer which periodically "ticks" and allows the code to inspect that current state of the alarm clock.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var durationLabel: UILabel!
#IBOutlet weak var cycleButton: UIButton!
#IBOutlet weak var resetButton: UIButton!
let alarmClock: AlarmClock = {
let clock = AlarmClock()
clock.timeout = 10.0
return clock
}()
var timer: Timer? = nil
var durationFormatter: DateComponentsFormatter {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.minute, .second]
formatter.unitsStyle = .abbreviated
return formatter
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func cycleClock(_ sender: Any) {
alarmClock.isRunning = !alarmClock.isRunning
if alarmClock.isRunning {
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(tick), userInfo: nil, repeats: true)
} else {
timer?.invalidate()
timer = nil
}
updateDurationLabel()
updateCycleButtonState()
}
#IBAction func restartClock(_ sender: Any) {
timer?.invalidate()
timer = nil
alarmClock.reset()
updateDurationLabel()
updateCycleButtonState()
}
func updateCycleButtonState() {
if alarmClock.isRunning {
cycleButton.setTitle("Pause", for: [])
} else {
cycleButton.setTitle("Start", for: [])
}
}
func updateDurationLabel() {
durationLabel.text = "\(durationFormatter.string(from: alarmClock.duration)!)/\(durationFormatter.string(from: alarmClock.timeRemaining)!)"
}
#objc func tick() {
print("click")
updateDurationLabel()
if alarmClock.hasExpired {
restartClock(self)
}
}
}
Now, you could also add some kind of "internal" thread to periodically check the state of the clock and call a delegate which could then bee used to update the UI, but the intention here is the decoupling of the concerns, and this means you're not adding yet another thread to the system unnecessarily (not saying you couldn't do, but it's just one more level of complexity I didn't want to add ;))

Stop and restart a timer

I want to stop this timer and then restart it from where I stopped it.
secondsTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(addSeconds), userInfo: nil, repeats: true)
Below, it was suggested I shouldn't increment a timer in my timer handler. Why not?
For example, using GCD timer:
func countSeconds() {
secondsTimer = DispatchSource.makeTimerSource(queue: .main)
secondsTimer?.schedule(deadline: .now(), repeating: 1.0)
secondsTimer?.setEventHandler { [weak self] in
self?.addSeconds()
}
}
#objc func addSeconds() {
seconds += 1
}
func startGame() {
secondsTimer?.resume()
}
We don't pause/resume Timer instances. We stop them with invalidate(). And when you want to restart it, just create new timer.
Please refer to the Timer documentation, also available right in Xcode.
Note that you can suspend and resume GCD timers, DispatchSourceTimer.
var timer: DispatchSourceTimer? // note, unlike `Timer`, we have to maintain strong reference to GCD timer sources
func createTimer() {
timer = DispatchSource.makeTimerSource(queue: .main)
timer?.schedule(deadline: .now(), repeating: 1.0)
timer?.setEventHandler { [weak self] in // assuming you're referencing `self` in here, use `weak` to avoid strong reference cycles
// do something
}
// note, timer is not yet started; you have to call `timer?.resume()`
}
func startTimer() {
timer?.resume()
}
func pauseTiemr() {
timer?.suspend()
}
func stopTimer() {
timer?.cancel()
timer = nil
}
Please note, I am not suggesting that if you want suspend and resume that you should use GCD DispatchSourceTimer. Calling invalidate and recreating Timer as needed is simple enough, so just do that. I only provide this GCD information for the sake of completeness.
By the way, as a general principle, never "increment" some counter in your timer handler. That's a common mistake. Timers are not guaranteed to fire every time or with exact precision. Always save some reference time at the start, and then in your event handler, calculate differences between the current time and the start time. For example, extending my GCD timer example:
func createTimer() {
timer = DispatchSource.makeTimerSource(queue: .main)
timer?.schedule(deadline: .now(), repeating: 0.1)
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second, .nanosecond]
formatter.zeroFormattingBehavior = .pad
timer?.setEventHandler { [weak self] in
guard let start = self?.start else { return }
let elapsed = (self?.totalElapsed ?? 0) + CACurrentMediaTime() - start
self?.label.text = formatter.string(from: elapsed)
}
}
var start: CFTimeInterval? // if nil, timer not running
var totalElapsed: CFTimeInterval?
#objc func didTapButton(_ button: UIButton) {
if start == nil {
startTimer()
} else {
pauseTimer()
}
}
private func startTimer() {
start = CACurrentMediaTime()
timer?.resume()
}
private func pauseTimer() {
timer?.suspend()
totalElapsed = (totalElapsed ?? 0) + (CACurrentMediaTime() - start!)
start = nil
}
I do it with this code:
var timer: Timer?
func startTimer() {
timer = .scheduledTimer(withTimeInterval: 4, repeats: false, block: { _ in
// Do whatever
})
}
func resetTimer() {
timer?.invalidate()
startTimer()
}
You can start, stop and reset timer in swift4
class ViewController: UIViewController {
var counter = 0
var timer = Timer()
var totalSecond = 20
#IBOutlet weak var label1: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func start_btn(_ sender: Any) {
timer.invalidate() // just in case this button is tapped multiple times
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}
#IBAction func stop_btn(_ sender: Any) {
do {
self.timer.invalidate()
}
func timeFormatted(_ totalSeconds: Int) -> String {
let seconds: Int = totalSeconds % 60
return String(format: "0:%02d", seconds)
}
}
#IBAction func reset_btn(_ sender: Any) {
timer.invalidate()
//timerAction()
counter = 0
label1.text = "\(counter)"
}
#objc func timerAction()
{
counter += 1
label1.text = "\(counter)"
}
}
You can declare the Timer as 'weak var' instead of just 'var' like:
weak var timer: Timer?
Now you can pause your timer with:
timer?.invalidate()
To resume:
timer?.fire()

Timer scope issue with #selector

I'm stuck with getting this into the right scope. I'm sure its something super simple but I'm banging my head against a wall with it. any answers i'm finding are in earlier version of swift so im struggling to understand how to solve this
My current issue is trying to get the timer initialised correctly and counting. the "selector" is causing the most issues. the rest i'm sure ill be able to figure out afterwards
code is as follows.
#IBOutlet weak var shortTimerLabel: UILabel!
#IBOutlet weak var longTimerLabel: UILabel!
var seconds = 60 //This variable will hold a starting value of seconds. It could be any amount above 0.
var timer = Timer()
var isTimerRunning = false //This will be used to make sure only one timer is created at a time.
#IBAction func longpressed(_ gestureRecognizer: UILongPressGestureRecognizer) {
shortTimerLabel.text = "longPressed"
}
#IBAction func tappedShortTimer(_ gestureRecognizer: UITapGestureRecognizer) {
shortTimerLabel.text = "ShortPressed"
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
}
func updateTimer() {
seconds += 1 //This will decrement(count down)the seconds.
shortTimerLabel.text = "\(seconds)" //This will update the label.
}
}
im trying to create a stopwatch which can be controlled using gestures. short pressed on label for stop/start and long press to reset time.
In your updateTimer() method, the first line should read seconds -= 1 instead (if you want to count down).
Also, you may want to update your updateTimer() method like this:
func updateTimer() {
seconds -= 1
if seconds == 0 {
timer.invalidate()
isTimerRunning = false
}
shortTimerLabel.text = String(describing: seconds)
}
An other issue here is that you added your runTimer() and updateTimer() method to the wrong place. You should not add them inside your viewDidLoad method.
Your final code would look like this:
var seconds = 60
var timer = Timer()
var isTimerRunning = false
#IBAction func longpressed(_ gestureRecognizer: UILongPressGestureRecognizer) {
resetTimer()
}
#IBAction func tappedShortTimer(_ gestureRecognizer: UITapGestureRecognizer) {
stopStartTimer()
}
override func viewDidLoad() {
super.viewDidLoad()
// ...
}
func stopStartTimer() {
if !isTimerRunning {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
isTimerRunning = true
} else {
timer.invalidate()
isTimerRunning = false
}
}
func updateTimer() {
seconds -= 1
if seconds == 0 {
timer.invalidate()
isTimerRunning = false
}
shortTimerLabel.text = String(describing: seconds)
}
func resetTimer() {
if isTimerRunning {
seconds = 60
timer.invalidate()
isTimerRunning = false
stopStartTimer()
}
}
The selector should be given in the form #selector(ViewController.updateTimer)
You shouldn't declare functions in the viewDidLoad but outside
You only set the timer in the longpressed function
For stoping it is timer.invalidate()

Timer Not Stopping When Supposed To Swift 2

I have coded my timer with the idea in mind that when my timer reaches 10, it stops. But for some reason, it doesn't.
import Foundation
import UIKit
class SinglePlayer: UIViewController {
var timerCount = 0.0
#IBOutlet weak var timer: UILabel!
var timerVar = NSTimer()
func isCounting() {
timerCount += 0.1
timer.text = "\(timerCount)"
}
override func viewDidLoad() {
super.viewDidLoad()
if timerCount <= 10.0{
timerVar = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "isCounting", userInfo: nil, repeats:true)
} else {
timerVar.invalidate()
}
}
}
You need to put condition in the callback function and not on viewDidLoad which is only called once on the load.
func isCounting() {
timerCount += 0.1
timer.text = "\(timerCount)"
if timerCount >= 10.0 {
timerVar.invalidate()
}
}