So I have a program which is a stopwatch and I have the stopwatch is working but when I press the pause button the thing pauses but when I press the start button again to start the stopwatch from where it left off it resets instead I have tried multiple thing but nothing seems to work can you help me.
below is my code that I have for my reset, start and pause function they are all IBactions and Outlets.
I think the problem is with the start or pause buttons
#IBOutlet weak var startButton: UIButton!
#IBOutlet weak var pauseButton: UIButton!
#IBAction func startTimer(_ sender: AnyObject) {
if(isPlaying) {
return
}
startButton.isEnabled = false
pauseButton.isEnabled = true
isPlaying = true
let aSelector : Selector = #selector(ViewController.updateTime)
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
counter = NSDate.timeIntervalSinceReferenceDate
}
#IBAction func pauseTimer(_ sender: AnyObject) {
startButton.isEnabled = true
pauseButton.isEnabled = false
timer.invalidate()
isPlaying = false
}
#IBAction func resetTimer(_ sender: AnyObject) {
startButton.isEnabled = true
pauseButton.isEnabled = false
timer.invalidate()
isPlaying = false
counter = 0.0
timeLabel.text = String("00:00:00:00")
}
then I also have my Updatetimer part which I'm sure works fine but f you need it just ask!!
If you need more info or specifications just ask or leave a comment.
here is my update timer
#objc func updateTime() {
let currentTime = NSDate.timeIntervalSinceReferenceDate
//Find the difference between current time and start time.
var elapsedTime: TimeInterval = currentTime - counter
//calculates the hour in elapsed time
let hours = UInt8(elapsedTime / 3600.0)
elapsedTime -= (TimeInterval(hours) * 3600.0)
//calculate the minutes in elapsed time.
let minutes = UInt8(elapsedTime / 60.0)
elapsedTime -= (TimeInterval(minutes) * 60)
//calculate the seconds in elapsed time.
let seconds = UInt8(elapsedTime)
elapsedTime -= TimeInterval(seconds)
//find out the fraction of milliseconds to be displayed.
let fraction = UInt8(elapsedTime * 100)
//add the leading zero for minutes, seconds and millseconds and store them as string constants
let strHours = String(format: "%02d", hours)
let strMinutes = String(format: "%02d", minutes)
let strSeconds = String(format: "%02d", seconds)
let strFraction = String(format: "%02d", fraction)
//concatenate minuets, seconds and milliseconds as assign it to the UILabel
timeLabel.text = "\(strHours):\(strMinutes):\(strSeconds):\(strFraction)"
}
The problem is in this line counter = NSDate.timeIntervalSinceReferenceDate in your startTimer() function. You should only set the counter if the counter == 0.0. So change your code such as following:
if counter == 0.0{
counter = NSDate.timeIntervalSinceReferenceDate
}else{
counter = previousDate.timeIntervalSinceReferenceDate
}
also add the following line to your pause function, so it saves the date that counter was paused, so the next time the counter will start from that point:
var previousDate = NSDate()
#IBAction func pauseTimer(_ sender: AnyObject){
//...Your other code...
previousDate = NSDate()
}
Also your update function should use the saved date previousDate for updating the counter.
That should fix the problem.
Related
I have a pickerController set the hours, minutes and seconds but I don't know how to set up my timer, this is the Code I have, but it doesn't work, how can I change that? Thank's in advance
func startTimer() {
if timer.isValid == true || timeHours == 0 || timeMinutes == 0 || timeSeconds == 0 {
//do nothing
} else {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(onTimerFires), userInfo: nil, repeats: true)
}
}
#objc func onTimerFires() {
timeHours -= 1
timeMinuteLabel.text = timeFunc()
if timeSeconds == 0 {
timer.invalidate()
}
}
func timeFunc() -> String {
let hours = Int(optionalValuePickerView.selectedRow(inComponent: 0))
let minutes = Int(optionalValuePickerView.selectedRow(inComponent: 1))
let seconds = Int(optionalValuePickerView.selectedRow(inComponent: 2))
return String(format: "%02i:%02i:%02i", hours, minutes, seconds)
}
There are a few issues here:
Every time onTimerFires is called, it is decrementing timeHours (which does not appear to be set or used anywhere) and then calls timeFunc, which just grabs values from the picker (which are not changing anywhere) to build the string.
This pattern of decrementing a counter in a timer makes a big assumption, namely that the timer is invoked exactly at the right time and that it will not ever miss a timer call. That is not a prudent assumption, unfortunately.
I would advise against the Selector based timer, as that introduces a strong reference cycle with the target. The block-based rendition with weak references is easier to avoid these sorts of cycles.
I would suggest a different pattern, namely that you save the time to which you are counting down:
var countDownDate: Date!
func updateCountDownDate() {
let components = DateComponents(hour: pickerView.selectedRow(inComponent: 0),
minute: pickerView.selectedRow(inComponent: 1),
second: pickerView.selectedRow(inComponent: 2))
countDownDate = Calendar.current.date(byAdding: components, to: Date())
}
Then your timer handler can calculate the amount of elapsed time to the target count down date/time:
func handleTimer(_ timer: Timer) {
let remaining = countDownDate.timeIntervalSince(Date())
guard remaining >= 0 else {
timer.invalidate()
return
}
label.text = timeString(from: remaining)
}
When you start the timer, you calculate the countDownDate and schedule the timer:
weak var timer: Timer?
func startTimer() {
timer?.invalidate() // if one was already running, cancel it
updateCountDownDate()
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
guard let self = self else {
timer.invalidate()
return
}
self.handleTimer(timer)
}
timer?.fire()
}
And, for what is worth, when preparing the string, you certainly can determine the date components between to dates (i.e., now and your target date/time), but you can also use a DateComponentsFormatter do this for you:
let formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second]
formatter.zeroFormattingBehavior = .pad
return formatter
}()
func timeString(from interval: TimeInterval) -> String? {
return formatter.string(from: interval)
}
I am getting a problem with Swift timer. Timer are not working properly for old iPhone models like iPhone 6. When I printed out time, timer counts every 2 second as 1 second. But if i change withTimeInterval as 0.05, it works a bit better. But still is not work as real life time. Here is my code. Can anyone help me ?
weak var timer: Timer?
var startTime : TimeInterval!
var elapsingTime: Double = 0.0
func configureTimer(totalSecond: Double) {
startTime = Date().timeIntervalSinceReferenceDate
self.invalidateTimer()
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 0.02,target: self,selector: #selector(self.advanceTimer(timer:)),userInfo: nil,repeats: true)
}
}
#objc func advanceTimer(timer: Timer) {
elapsingTime += 0.02
self.questionView.configureProgressBar(totalTime: Double(self.totalSecond), elapsingTime: elapsingTime)
self.isTimeExpired = false
self.elapsingTime = Date().timeIntervalSinceReferenceDate - self.startTime
if Int(elapsingTime) == Int(totalSecond) {
self.timer!.invalidate()
self.isTimeExpired = true
self.userAnswerIndex = -1
self.sendAnswer(index: self.userAnswerIndex, isTimeExpired: self.isTimeExpired)
}
}
Timers are not very precise. There precisions is about 0.05 seconds I think. And if a process require a lot of power, your timer will be even more slowed down. The solution can be to save the time when you start your timer and that each time your timer fire, you do a mathematical operation to know how many time passed :
class YourClass {
var startTime : TimeInterval!
//...
func configureTimer() {
startTime = Date().timeIntervalSinceReferenceDate //You add this line
elapsingTime = 0.0
timer?.invalidate()
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { [weak self] timer in
guard let self = self else { return }
self.elapsingTime = Date().timeIntervalSinceReferenceDate - self.startTime //You change this one
self.questionView.configureProgressBar(totalTime: self.totalTime, elapsingTime: self.elapsingTime)
self.questionView.videoQuestionPlayer.player?.play()
if Int(self.elapsingTime) == Int(self.totalTime) {
self.timer!.invalidate()
self.isTimeExpired = true
self.sendAnswer(index: -1)
}
})
}
}
}
I would try setting it up like this.
var myTimer = Timer()
var counter = 0.0
myTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(yourTargetFunctionHere), userInfo: nil, repeats: true)
#objc func yourTargetFunctionHere() {
//in here is where you do all your code work
counter += 0.1
if counter == yourElapsedTime {
//when your target time is hit do something here
}
if counter == finishedTime {
self.timer.invalidate()
self.counter = 0
}
}
I am a school teacher and we have been 'ordered' to use a specific timer for our lessons, which low and behold doesn't work on our apple iMac's. I am trying to create my own in xcode and so far have created a basic window which will countdown a label in seconds. I have at the moment assigned the buttons and they work (in increments of 60 seconds).
This works and is fine but ideally i would like the label to display minutes and seconds instead (much easier for the kids). What is the best way to code this? Last time i used xcode was in 2009 and i am way out of date now!! Thanks in advance
--
#objc func updateTimer() {
seconds -= 1 //This will decrement(count down)the seconds.
countdownLabel.stringValue = "\(seconds)" //This will update the label.
}
--
#objc func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector:(#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
}
--
#IBAction func threeMin(_ sender: Any) {
seconds = 180
runTimer()
}
--
There are many solutions. A convenient one is DateComponentsFormatter
let formatter : DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.minute, .second]
return formatter
}()
#objc func updateTimer() {
seconds -= 1
countdownLabel.stringValue = formatter.string(from: TimeInterval(seconds))!
}
Some improvements:
Assign tags to all buttons with their value in seconds for example set the tag of the threeMin button to 180. Then use only one IBAction and connect all buttons to that action.
In the action first check if the timer is running and start it only if it's not running
var timer : Timer?
#IBAction func startTimer(_ sender: NSButton) {
if timer == nil {
seconds = sender.tag
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.updateTimer), userInfo: nil, repeats: true)
}
}
Create a function to stop the timer reliably
func stopTimer() {
if timer != nil {
timer?.invalidate()
timer = nil
}
}
In the updateTimer() function stop the timer if seconds is 0
#objc func updateTimer() {
seconds -= 1
countdownLabel.stringValue = formatter.string(from: TimeInterval(seconds))!
if seconds == 0 { stopTimer() }
}
Im trying to make a countdown timer. everything works fine except that my timer does not count at regular intervals (1sec); instead it counts all the way down instantly giving me 0 every time. did a lot of search without luck. All examples I could find show similar timeInterval parameter.
var timer = Timer()
var remainingTime = 120
#objc func timerCount () {
if remainingTime > 0 {
while remainingTime > 0 {
remainingTime -= 1
timerLabel.text = String(remainingTime)
print(remainingTime)
}
} else {
timer.invalidate()
}
}
#IBAction func pauseButton(_ sender: Any) {
timer.invalidate()
}
#IBAction func playButton(_ sender: Any) {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerCount), userInfo: nil, repeats: true)
}
The reason your code is not working is that you have put an unnecessary while loop in your timerCount() method. You don't need to do this. Your timer will fire this method after each time interval. At very first call this while loop make your remainingTime to 0. This is why you are instantly getting 0 every time.
You just need to remove that while loop.
Can You try like this
var timer = Timer()
var remainingTime = 120
#objc func timerCount () {
let date = NSDate()
let nowdate = UserDefaults.standard.object(forKey: "NowDate") as! Date
let miniute = nowdate.timeIntervalSince(date as Date)
print(Int(miniute))
if (Int(miniute) == 0) {
timer.invalidate()
UserDefaults.standard.removeObject(forKey: "NowDate")
} else {
timerLabel.text = String(Int(miniute))
}
}
#IBAction func pauseButton(_ sender: Any) {
timer.invalidate()
UserDefaults.standard.removeObject(forKey: "NowDate")
}
#IBAction func playButton(_ sender: Any) {
let CurrentDate = NSDate()
let NowDate = CurrentDate.addingTimeInterval(TimeInterval(remainingTime))
UserDefaults.standard.set(NowDate, forKey: "NowDate")
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerCount), userInfo: nil, repeats: true)
}
I am attempting to have my UILabel update based on the duration of what the AudioPlayer current time is. I don't want to use a timer, because within my app people will be recording themselves with small durations & the half seconds are important, so I wouldn't really want to use timer(). Instead what I am attempting to do is just have the label update based on what the current time is automatically. I can't however seem to figure it out...
Here is how I am going about it, but clearly I am not doing it correctly
var audioPlayer : AVAudioPlayer!
var audioTime = UILabel() {
didSet {
do {
try audioPlayer = AVAudioPlayer(contentsOf: audioURL!)
let currentTime = Int(audioPlayer.currentTime)
let minutes = currentTime/60
let seconds = currentTime - minutes * 60
audioTime.text = (NSString(format: "%02d:%02d", minutes,seconds) as String)
} catch {
}
}
}
This might help you! Connect Outlet to showTimeLbl and try.
class ViewController: UIViewController {
#IBOutlet weak var showTimeLbl: UILabel!
var timeCount:Int = 0
var timer:Timer!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if(timer != nil)
{
timer.invalidate()
}
timer = Timer(timeInterval: 1.0, target: self, selector: #selector(ViewController.timerDidFire), userInfo: nil, repeats: true)
RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)
}
func timerDidFire()
{
timeCount += 1
showTimeLbl.text = NSString(format: "%02d:%02d:%02d", timeCount/3600,(timeCount/60)%60,timeCount%60) as String
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
timer?.invalidate()
timer = nil
}