NSTimer triggered twice after switching view controllers - ios

I'm trying to keep accurate counting and display of a countdown timer between switching view controllers. In order to do so, I added NSNotifications as mentioned in this question: Timer Label not updated after switching views (swift)
The problem is that the timer will be decremented twice after switching back from the other view controller.
This issue seems to be unrelated to the notifications, as the same problem occurs without them, it only becomes apparent after restarting the timer as it isn't updated automatically when reaching the view controller.
I really don't find the cause of this, any help much appreciated!
I've set up this sample code. There's another view controller added to the original one in Main.storyboard, there is one switch and one label displaying the timer added to it. The original view controller only contains one bar button item to trigger the segue to the second view controller.
import Foundation
final class DataModel: NSObject {
static let shared = DataModel()
var isSleepTimerOn = false
var timerTime: NSTimeInterval = 100.0
}
// The second view controller.
import UIKit
class TimerViewController: UIViewController {
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var timerSwitch: UISwitch!
var timer: NSTimer?
override func viewDidLoad() {
super.viewDidLoad()
timerSwitch.on = DataModel.shared.isSleepTimerOn
timerLabel.text = String(DataModel.shared.timerTime)
let selector = #selector(setTimerLabel), name = "setTimerLabel"
NSNotificationCenter.defaultCenter().addObserver(self, selector: selector, name: name, object: nil)
}
#IBAction func switchToggled(sender: AnyObject) {
DataModel.shared.isSleepTimerOn = timerSwitch.on
switch timerSwitch.on {
case true:
startTimer()
case false:
stopTimer()
}
}
func startTimer() {
let selector = #selector(decrementTimer)
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: selector, userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer!, forMode: NSRunLoopCommonModes)
}
func decrementTimer() {
DataModel.shared.timerTime -= 1
NSNotificationCenter.defaultCenter().postNotificationName("setTimerLabel", object: nil)
setTimerLabel()
}
func setTimerLabel() {
timerLabel.text = String(DataModel.shared.timerTime)
}
func stopTimer() {
timer?.invalidate()
timer = nil
DataModel.shared.timerTime = 100.0
timerLabel.text = String(DataModel.shared.timerTime)
NSNotificationCenter.defaultCenter().removeObserver(self, name: "setTimerLabel", object: nil)
}
}
EDIT: Solution: move the timer from the view controller class to the DataModel singleton, so there would be only one timer.

Ok, new morning, fresh brain, had another look at the code and the problem was clear: I was initiating a new timer every time I switched to the view controller, so when stopping the timer, it wouldn't stop the previous one. Since there should be only one timer I moved it from the view controller class to the DataModel, which is a singleton.

Related

Function when WatchKit timer ends

I have a watchkit timer,
However, I have no idea how to perform an action like a notification or segue when the timer ends.
Any ideas?
#IBOutlet var sceneTimer: WKInterfaceTimer!
override func awake(withContext context: Any?) {
super.awake(withContext: context)
sceneTimer.setDate(NSDate(timeIntervalSinceNow :21) as Date)
}
override func willActivate() {
super.willActivate()
sceneTimer.start()
}
override func didDeactivate() {
super.didDeactivate()
}
Yes I am new to this whole thing so its not a ground-breaking code, feel free to correct.
Someone could certainly improve upon this I'm sure. But I built a little watchOS App with a Start Button, a timer and a label. Hook up your WKInterfaceTimer, WKInterfaceLabel and Button as appropriate and this code should work. You can also download the project from GitHub.
var theTimer = Timer()
var backgroundTimer = TimeInterval(15)
#IBOutlet var appleTimer: WKInterfaceTimer!
#IBOutlet var label: WKInterfaceLabel!
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
appleTimer.setDate(Date(timeIntervalSinceNow: 15))
label.setText("")
}
#IBAction func startButton() {
let startTime = Date(timeIntervalSinceNow: 15)
appleTimer.setDate(startTime)
appleTimer.start()
// This will call timerCountDown() once per second until conditions are met.
theTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerCountDown), userInfo: nil, repeats: true)
}
func timerCountDown() {
backgroundTimer -= 1.0
print(backgroundTimer)
if backgroundTimer == 0 {
theTimer.invalidate()
appleTimer.stop()
// You could call an Alert Action here.
label.setText("Timer Done!")
}
}
WKInterfaceTimer is just a label that can be used to display a countdown. It has no associated function that is called by the system once the countdown reaches zero. You need to configure a Timer object with the same target date if you need to know when the countdown reaches 0.

value from func from other viewController

I'm trying to write simple clicker but I have some problem, on "StartViewController" I have func value() it add per sec 1 + click 1 + last save value and I have shopViewController on the shopViewController I have button when I press it it must give +1(every time) to click but how can I access to current value from func value()? When I try get current value I get nil
func value() -> Float {
let endValue = appPerSec + valueToLoad + click
valueToSave = endValue
valueLable.text = "\(endValue)"
return valueToSave
}
// shopViewController
var StartView:StartViewController!
var currentValue:Float! = 0.0
#IBAction func testAdd(_ sender: Any) {
currentValue = StartView.value // here I get NIL
print(currentValue)
}
i did not UnderStand your question but i can give solution in parts for the terms i can read, or please improve your question so that i can understand exactly what you are looking for.
meanwhile i'll just give some concepts that i think you are looking for, if not useful please edit your question before rating not useful :
to make a timer you can use
#IBOutlet weak var timeInTimer: UILabel!
var timer = Timer()
#IBAction func buttonPressed(_ sender: Any) //to stop the timer
{
timer.invalidate()
}
#IBAction func playButtonPressed(_ sender: Any) //to start the timer
{
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.processTimer), userInfo: nil, repeats: true)
}
To pass data from one view to other view :
let anyObjectName = storyboard?.instantiateViewController(withIdentifier: "IdentifierOfSecondViewController") as! IdentifierOfSecondViewController
anyObjectName.userName = userName //where username is a variable in second view
You can use Delegate and Protocols. Above your shopView add:
protocol ShopProtocol{
func updateCount();
}
In your shopView add:
var delegateShop:ShopProtocol?
And when you change that variable, go to:
delegateShop.updateCount()
In your main view controller, when you present this shopViewController (before presenting it), add:
shopController.delegateShop = self
And change your ViewController definition to
class ...: UIViewController, ..., ShopProtocol{
And, of course, create in that view controller:
func updateCount(){
//do Stuff
}

How do I get NSTimer to restart updating a label when returning to a ViewController?

I have a basic Timer app (the start of something a bit more complex) and I'm trying to allow the user to open a new view, change some settings, then return to the time screen and have the timer still ticking down.
At the moment, when the user returns to the timer screen ViewController, my timer is still firing and printing the time to the console, but the label has stopped updating.
How do I start the timer updating the label again?
class Countdown
{
var initial = 100
var count = 100
var timer: NSTimer!
var timerRunning = false
}
var globals = Countdown()
class ViewController: UIViewController {
#IBOutlet weak var timer1Label: UILabel!
#IBAction func unwindSegue(sender: UIStoryboardSegue) {} // Added to get rewind segue to work as recommended by Ben Sullivan
#IBAction func timer1(sender: AnyObject)
{
globals.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(ViewController.timerRunning), userInfo: nil, repeats: true)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func timerRunning()
{
globals.timerRunning = true
let minutes = (globals.count / 60) % 60;
let seconds = globals.count % 60
print("\(minutes):\(seconds)")
globals.count = globals.count - 1
timer1Label.text = String("\(minutes):\(seconds)")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
At the moment, the settings view controller has nothing on it - just a button that returns the user to ViewController.
The issue is that you are performing new segue to your timer view controller. This creates a new instance of your timer view controller rather than segueing back to the original one.
First delete the segue you have setup from your second to first controller. You will then need to use an unwind segue, you can learn how to do this by following the link below. (I had the same issue as yourself recently and this is what I was provided also)
https://developer.apple.com/library/ios/technotes/tn2298/_index.html
By using the unwind segue you shouldn't need to add any additional code as the label on your original controller should still be getting updated.

Stopwatch iOS app w/ concurrent process bug

This is my first post so I hope this is a valid question. I've searched the forums for an answer to this with no luck. Below is my code for a stopwatch app. Problem I am having is when the play button is clicked multiple times it is ticking multiple seconds at a time. How do I safely stop this from happening?
ViewController
import UIKit
class ViewController: UIViewController {
// MARK: Properties
#IBOutlet weak var timerLabel: UILabel!
var timer = NSTimer()
var time = 0
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: Functions
func increaseTimer() {
time++
let formattedTime = String(format:"%02d:%02d", (time/60), time%60)
timerLabel.text = "\(formattedTime)"
}
// MARK: Actions
#IBAction func btnPlay(sender: AnyObject) {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self,
selector: Selector("increaseTimer"), userInfo: nil, repeats: true)
}
#IBAction func btnStop(sender: AnyObject) {
timer.invalidate()
}
#IBAction func btnReset(sender: AnyObject) {
timer.invalidate()
time = 0
timerLabel.text = "00:00"
}
}
EDIT: SOVED http://imgur.com/mqw1Xnp
Make your button into a start/stop button. Use a boolean instance variable to keep track of whether the timer is running or not. If it is, stop it. If it's not, start it.
Alternately, make the code that starts the timer set button.disabled = true

Simple swift Stopwatch don't work

This is my code.
//
// ViewController.swift
// Stopwatch
//
// Created by Orkun Atasoy on 12.09.15.
// Copyright (c) 2015 Orkun Atasoy. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var timerButton: UIButton!
var timer : NSTimer?
var ms = 0
#IBAction func buttonTapped(sender: AnyObject) {
timerButton.setTitle("Stopp", forState:UIControlState.Normal)
self.timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
func update() {
self.ms++
timerLabel.text = String(self.ms)enter code here
}
}
The Problem is when i run the build it comes the introscreen with big text "Stopwatch" and the it is like freezed there. But it should come a label with button downside which has a text "start". When i click the button it should start counting and the label should change to "stopp". When i click again the it should stop the timer.
I dont get what the Problem ist. I am a Swift newbie. I would be pleased if you could help me.
Thank you for your attention
EDIT >> the label text is at the beginning "00:00".
Looks like you are messing with IBOutlet connections and I have corrected some of your code and here is working code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var timerButton: UIButton!
var timer : NSTimer?
var ms = 0
override func viewDidLoad() {
timerLabel.text = "00:00"
}
#IBAction func buttonTapped(sender: AnyObject) {
if timerButton.currentTitle == "Stopp" {
timer?.invalidate()
timerButton.setTitle("Start", forState:UIControlState.Normal)
} else {
timerButton.setTitle("Stopp", forState:UIControlState.Normal)
self.timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
}
func update() {
self.ms++
timerLabel.text = String(self.ms)
}
}
And First of all remove all outlets from your storyboard view controller and connect it this way:
And if you want check THIS sample project.
The method you call when the timer fires is incorrect. From the documentation:
The selector should have the following signature: timerFireMethod: (including a colon to indicate that the method takes an argument). The timer passes itself as the argument, thus the method would adopt the following pattern:
- (void)timerFireMethod:(NSTimer *)timer
So your method should be:
func update(timer: NSTimer) {
self.ms++
timerLabel.text = String(self.ms)
}
And you should set up your timer as:
NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "update:", userInfo: nil, repeats: true)
There are a few more things I could mention - such as no need to use self when unambigously using variables, and using a dispatch timer instead of an NSTimer, but this should at least solve your immediate problem.

Resources