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()
Related
So I'm trying to make a chess timer. I'm using string to make the 00:00 in a variable called storeTimed. Is there a way possible to make that used an timer to count down from that?
Here's my code:
The part I need help with is the updateTimer() function. Since the storedTime is a string trying to pass as a Int, xCode isn't liking it. I'm honestly not very sure what I could do. Mostly at the else statement, but the whole part in general
class ChessTimer: UIViewController {
#IBOutlet weak var playerTimer1: UILabel!
#IBOutlet weak var playerTimer2: UILabel!
var timer = Timer()
var time = 10
var isTimerRunning = false
var storedTime = "00:00"
override func viewDidLoad() {
super.viewDidLoad()
if isTimerRunning == false {
runTimer()
}
}
#IBAction func restartButton(_ sender: UIButton) {
}
#IBAction func pausePressed(_ sender: UIButton) {
timer.invalidate()
}
#IBAction func settingsPressed(_ sender: UIButton) {
performSegue(withIdentifier: "goToSettings", sender: self)
}
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self,selector: (#selector(ChessTimer.updateTimer)),userInfo: nil, repeats: true)
isTimerRunning = true
}
#objc func updateTimer() {
if Int(storedTime) < 1 {
timer.invalidate()
playerTimer1.text = "00:00"
playerTimer2.text = "00:00"
}
else {
Int(storedTime)! -= 1
playerTimer1.text = prodTimeString(time: TimeInterval(storedTime)!)
}
}
func prodTimeString(time: TimeInterval) -> String {
let prodMinutes = Int(time) / 60 % 60
let prodSeconds = Int(time) % 60
return String(format: "%02d:%02d", prodMinutes, prodSeconds)
}
#IBAction func playerButton1(_ sender: UIButton) {
}
#IBAction func playerButton2(_ sender: UIButton) {
}
#IBAction func unwindToVC1(sender: UIStoryboardSegue) {
if let settingsController = sender.source as? SettingsController {
playerTimer1.text = settingsController.storedTime
playerTimer2.text = settingsController.storedTime
storedTime = settingsController.storedTime
}
}
}
To keep things simple, here's the code. I have removed the functions that aren't relevant to the discussion - non-implemented buttons, etc.,
The simple idea is that you use numbers (Int / Double / whatever) to store variables that you are counting with, and then have helper functions to present the variable in different formats
import UIKit
class ChessTimer: UIViewController {
var timer = Timer()
var isTimerRunning = false
var storedTime : Int = 0 // use an integer to store the time
override func viewDidLoad() {
super.viewDidLoad()
if isTimerRunning == false {
runTimer()
}
}
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self,selector: (#selector(ChessTimer.updateTimer)),userInfo: nil, repeats: true)
isTimerRunning = true
}
#objc func updateTimer() {
// because we're storing the value in an Int, we don't need to convert it here
storedTime -= 1
if storedTime < 1 {
timer.invalidate()
// use the helper function to format the result for zero time as well as everything else
// that way, if you ever change it, you only have to change in one place
playerTimer1.text = prodTimeString(0)
playerTimer2.text = prodTimeString(0)
}
else {
playerTimer1.text = prodTimeString(storedTime)
}
}
func prodTimeString(_ time: Int) -> String {
// because there's only one parameter, and it's obvious, add '_' then you don't need the label when you call it
let prodMinutes = time / 60 % 60
let prodSeconds = time % 60
return String(format: "%02d:%02d", prodMinutes, prodSeconds)
}
}
I'm using Swift 5 in Xcode to code an app. On one view controller, I am creating a timer, which counts from 20 minutes down to 0. I have what I think is a successful code, but it throws back one error. In the line
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(SkippingViewController.updateTimer), userInfo: nil, repeats: true)
it gives an error saying Type 'SkippingViewController' has no member 'updateTimer' (SkippingViewController is the name of the view controller for the page of the app my timer is on)
How can I resolve this issue?
import UIKit
class SkippingViewController: UIViewController {
#IBOutlet weak var timeLabel: UILabel!
#IBOutlet weak var startWorkoutButton: UIButton!
#IBOutlet weak var pauseWorkoutButton: UIButton!
var timer = Timer()
var counter = 20.00
var isRunning = false
override func viewDidLoad() {
super.viewDidLoad()
timeLabel.text = "\(counter)"
startWorkoutButton.isEnabled = true
pauseWorkoutButton.isEnabled = false
// Do any additional setup after loading the view.
}
#IBAction func startWorkoutButtonDidTouch(_ sender: Any) {
if !isRunning {
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(SkippingViewController.updateTimer), userInfo: nil, repeats: true)
startWorkoutButton.isEnabled = false
pauseWorkoutButton.isEnabled = true
isRunning = true
}
}
#IBAction func pauseWorkoutButtonDidTouch(_ sender: Any) {
func updateTimer() {
counter -= 0.01
timeLabel.text = String(format: "%.01f", counter)
}
}
}
Your problem is, that there is no method called 'updateTimer' in SkippingViewController.swift. You falsely put the method inside of the method 'pauseWorkoutButtonDidTouch'. In order to resolve the error insert the following code into SkippingViewController.swift:
#objc func updateTimer() {
counter -= 0.01
timeLabel.text = String(format: "%.01f", counter)
}
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.
}
}
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()
working on my first stopwatch app.
I currently have a play button, pause button, and stop button.
I'd like to combine the play and pause button so that they switch back and forth.
My code looks like this:
var timer = NSTimer()
var count = 0
func updateTime() {
count++
time.text = "\(count)"
}
#IBAction func pauseButton(sender: AnyObject) {
timer.invalidate()
}
#IBOutlet weak var time: UILabel!
#IBAction func stopButton(sender: AnyObject) {
timer.invalidate()
count = 0
time.text = "0"
}
#IBAction func playButton(sender: AnyObject) {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateTime"), userInfo: nil, repeats: true)
}
Any help is appreciated.
Try adding booleans. See my code below.
#IBOutlet weak var label: UILabel!
var time = NSTimer()
var count = 0
var running = false
func result (){
count++
label.text = String(count)
println(count)
}
#IBAction func playpause(sender: AnyObject) {
if running == false {
time = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("result"), userInfo: nil, repeats: true)
running = true }
else {
time.invalidate()
running = false
}
}
Hope this helps!
You'll have a variable bound to the button, something like this:
#IBOutlet var thePlayPauseButton : UIButton!
This button will be linked with some action:
#IBAction func togglePlayPauseButton (button: UIButton) {
// If we are 'paused', then play:
if button.titleLabel!.text == "Pause" {
button.titleLabel!.text = "Play"
// do actual play ...
timer = NSTimer.scheduledTimerWithTimeInterval (1,
target: self,
selector: Selector("updateTime"),
userInfo: nil,
repeats: true)
}
else if button.titleLabel!.text == "Play" {
button.titleLabel!.text = "Pause"
// do actual pause ...
timer.invalidate()
}
else { /* error */ }
}
Of course, structurally you can use a switch//case and you can perform the toggle behavior by calling your preexisting pause and play methods.
I know this post is somewhat dated, but I was working with the same problem and I came up with a slightly different answer and wanted to share it to help others. Here is what I came up with in toggling the pause and play buttons.
class ViewController: UIViewController {
var time = NSTimer()
var seconds = 0
var running = false
func timer() {
seconds++
timeLabel.text = "\(seconds)"
}
func playing() {
time = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("timer"), userInfo: nil, repeats: true)
running = true
}
func pausing() {
time.invalidate()
running = false
}
#IBOutlet weak var timeLabel: UILabel!
#IBAction func stopButton(sender: AnyObject) {
time.invalidate()
seconds = 0
timeLabel.text = "0"
}
#IBAction func pausePlayToggleButton(sender: AnyObject) {
if running == false {
return playing()
} else {
return pausing()
}
}
I had both a pause and play button and I essentially took their effects and placed them in functions and used them as return values for a single button.