Swift NSTiimer not following specified Interval - ios

I am trying to create a quiz app which has a timer for each question when the timer expires (i.e. 10 seconds and I want Timer to have an interval of 1 sec) it resets it self and next question is fetched and Timer again restart from 10... But my issue is the timer doesn't follow a fixed interval when first question is loaded it shows interval of 2 ... i.e. 10,8,6 .. and then for second question it makes jump for 3 secs interval and similarly the interval increases.
import UIKit
class ViewController: UIViewController {
let allQuestions = QuestionsBundle()
var pickedAnswer : Int = 0
var questionCounter = 0
var score : Int = 0
var timer: Timer!
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var countDownLabel: UILabel!
#IBOutlet weak var ansLbl1: UILabel!
#IBOutlet weak var ansLbl2: UILabel!
#IBOutlet weak var ansLbl3: UILabel!
#IBOutlet weak var ansLbl4: UILabel!
#IBOutlet weak var checkBox1: CheckBox!
#IBOutlet weak var checkBox2: CheckBox!
#IBOutlet weak var checkBox3: CheckBox!
#IBOutlet weak var checkBox4: CheckBox!
var checkBoxlist : [CheckBox] = []
#IBAction func correct_Answer_Checbox_Btn(_ sender: AnyObject) {
//print("\(sender.tag) <==> \(String(describing: question?.correctAnswer))")
updateCheckBoxes(sender: sender)
if sender.tag == question?.correctAnswer{
question?.isAnswerCorrect = true
question?.selectedAnswer = sender.tag
//score = score + 1
}
else {
question?.isAnswerCorrect = false
}
}
func updateCheckBoxes(sender: AnyObject){
for checkBoxItem in checkBoxlist{
if checkBoxItem.tag != sender.tag {
checkBoxItem.isChecked = false
}
}
}
#IBOutlet weak var nextButton: UIButton!
#IBAction func nextBtnClicked(_ sender: AnyObject) {
do{
try handleNextQuestion()
}
catch{
moveToResultView()
}
}
func handleNextQuestion() throws {
nextQuestion()
if questionCounter == allQuestions.list.count-1{
finishButton.isHidden = false
nextButton.isHidden = true
//scoreLbl.text = "\(score)"
}
}
var question : Question?
var countTime = 10.0
override func viewDidLoad() {
super.viewDidLoad()
finishButton?.isHidden = true
checkBoxlist = fetchCheckBoxList()
question = fetchQuestion()
setQuizView(question: question!)
// Do any additional setup after loading the view, typically from a nib.
}
// set all questions in a function
#objc func update() {
if(countTime > 0) {
countTime = countTime - 1
self.countDownLabel.text = String(countTime)
}else{
timer.invalidate()
countTime = 10.0
do{
try handleNextQuestion()
}
catch{
moveToResultView()
}
}
}
func startTimer() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
}
func setQuizView(question:Question) {
self.countDownLabel.text = "10"
startTimer()
questionLabel.text = question.questionText
ansLbl1.text = question.answer1
ansLbl2.text = question.answer2
ansLbl3.text = question.answer3
ansLbl4.text = question.answer4
if question.selectedAnswer == Constants.DEFAULT_ANSWER {
for checkBoxItem in checkBoxlist{
checkBoxItem.isChecked = false
}
}
}
#IBOutlet weak var finishButton: UIButton!
// prepare segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == resultScreenIdentifier{
let vc = segue.destination as! ResultViewController
vc.data = sender as! String
}
}
let resultScreenIdentifier = "resultScreenSegue"
func moveToResultView(){
performSegue(withIdentifier: resultScreenIdentifier, sender: score)
}
#IBAction func finishButtonClicked(_ sender: UIButton) {
//perform segue
let score = "\(calculateScore())"
moveToResultView()
}
// calculate the score of quiz using loop
func calculateScore()->Int{
var numOfCorrectAnswers = 0
for question in allQuestions.list{
if question.isAnswerCorrect {
numOfCorrectAnswers = numOfCorrectAnswers + 1
//print(numOfCorrectAnswers)
}
}
return numOfCorrectAnswers
}
func nextQuestion(){
showResultView(isCorrect: (question?.isAnswerCorrect)!)
questionCounter = questionCounter + 1
question = fetchQuestion()
setQuizView(question: question!)
}
func fetchQuestion() -> Question{
return allQuestions.list[questionCounter]
}
func fetchCheckBoxList() -> [CheckBox]{
let arr : [CheckBox] = [checkBox1,checkBox2,checkBox3,checkBox4]
return arr
}
}

Timers are not particularly accurate. They can suffer from significant jitter.
A better approach is to create a Date that represents the expiration time (ie Date(timeIntervalSinceNow:10) and then run a Timer with a much shorter interval (I would suggest around 0.1 second). You can then calculate the time remaining based on the target Date and check if the target date is in the past.

Related

Is it possible to toss data to another view controller?

I just Make timer that can use in life. just like image that I push in here, if I go back to main ViewController then I wanna the number that I input in set view controller are tossed to viewController so when I go back to main ViewController and press restart then that number gonna be in text of CountTimeLabel.. but I really don't know how to toss data that I input in another view controller to root viewController... pleas help me.. if I write code like ViewController().variableName = 30 in setViewController, that dose not make things well..(I already know about prepare function but that is not what I am finding..because this is happen when I go back to ViewController(RootViewController)) I will put my code in below..
is it possible to toss data to another view controller from other view controller?
import UIKit
class ViewController: UIViewController{
#IBOutlet var AllTileLabel: UILabel!
#IBOutlet var SumTimeLabel: UILabel!
#IBOutlet var CountTimeLabel: UILabel!
#IBOutlet var StartButton: UIButton!
#IBOutlet var StopButton: UIButton!
#IBOutlet var ResetButton: UIButton!
var timeTrigger = true
var realTime = Timer()
var second : Int = 3000
var sum : Int = 14400
var allTime : Int = 14400
var IntSecond : Int = 0
var ifReset = false
override func viewDidLoad() {
StartButton.layer.cornerRadius = 10
StopButton.layer.cornerRadius = 10
ResetButton.layer.cornerRadius = 10
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func StartButtonAction(_ sender: UIButton) {
if timeTrigger { checkTimeTrigger() }
print("Start")
}
#IBAction func StopButtonAction(_ sender: UIButton) {
endGame()
}
#IBAction func ResetButtonAction(_ sender: UIButton) {
print(second)
getTimeData()
//second = 3000
//CountTimeLabel.text = "0:50:00"
CountTimeLabel.text = printTime(temp: second)
ifReset = true
}
#IBAction func Reset(_ sender: UIButton) {
endGame()
timeTrigger = true
realTime = Timer()
second = 3000
sum = 14400
allTime = 14400
IntSecond = 0
ifReset = false
AllTileLabel.text = "8:00:00"
SumTimeLabel.text = "0:0:0"
CountTimeLabel.text = "0:50:00"
}
#objc func updateCounter(){
// if String(format: "%.2f",second) == "0.00"{
if second < 1 {
endGame()
CountTimeLabel.text = "종료"
} else {
second = second - 1
sum = sum + 1
allTime = allTime - 1
AllTileLabel.text = printTime(temp: allTime)
SumTimeLabel.text = printTime(temp: sum)
CountTimeLabel.text = printTime(temp: second)
print("update")
}
}
func checkTimeTrigger() {
realTime = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCounter), userInfo: nil, repeats: true)
timeTrigger = false
}
func endGame() {
realTime.invalidate()
timeTrigger = true
}
func printTime(temp : Int) -> String
{
let S = temp%60
let H = temp/3600
let M = temp/60 - H*60
let returnString = String(H) + ":" + String(M) + ":" + String(S)
return returnString
}
func getTimeData() {
second = 20
sum = SetViewController().real.sum
allTime = SetViewController().real.allTime
print(second)
}
}
import UIKit
class SetViewController: UIViewController {
#IBOutlet var View1: UIView!
#IBOutlet var View2: UIView!
#IBOutlet var InputView1: UIView!
#IBOutlet var InputView2: UIView!
#IBOutlet var SetButton: UIButton!
#IBOutlet var H1TextField: UITextField!
#IBOutlet var M1TextField: UITextField!
#IBOutlet var H2TextField: UITextField!
#IBOutlet var M2TextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
H1TextField.keyboardType = .numberPad
M1TextField.keyboardType = .numberPad
H2TextField.keyboardType = .numberPad
M2TextField.keyboardType = .numberPad
View1.layer.cornerRadius = 14
View2.layer.cornerRadius = 14
InputView1.layer.cornerRadius = 10
InputView2.layer.cornerRadius = 10
SetButton.layer.cornerRadius = 10
// Do any additional setup after loading the view.
}
#IBAction func SetButton(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
enter image description here
If you're a hobbyist programmer and you just want to "get it done", simply use a static.
Let's say Bottom: UIViewController is the "main", root, view controller at the absolute "base" of your app. no matter what happens "Bottom" is always there.
Say Timer: UIViewController is (any) other view controller you put on top for some reason.
In Bottom, do this
class Bottom: UIViewController, etc ... {
static weak var current: Bottom? = nil
override func viewDidLoad() {
super.viewDidLoad()
Bottom.current = self
}
func testing() {
print("it works, WTH")
}
Note that in ViewDidLoad, you simply set it.
Next, say you are in Timer, try this:
class Timer: UIViewController, etc ... {
func someFunction() {
Bottom.current.testing() // it's that easy
}
It's that easy.
Note there is a huge amount of confusion about using statics, singletons, and similar approaches in iPhone programming.
(Just for example, many engineers will say "avoid singletons!" This is remarkably confused because in iOS engineering, almost everything is a singleton (notably the app itself (!!!!!), the screen, the GPS, etc etc.)
In any event, as a beginner hobbyist, learn how to use statics (it's simple .. Bottom.current. ... as above), and eventually you can learn about the pros and cons of such things.

Swift jitterclick game will not reset

I am new to learning swift and my jitterclick style game will not reset after it is complete. Despite already setting the isTimerRunning to false, nothing i have done seems to work. The game is supposed to begin when you press start, start a count down timer from 10, count your number of clicks, and give you a click per second score at the end. This is my first app without a tutorial and appreciate all the help i can get. Thanks
import UIKit
var score = 0
var timer = Timer()
var cps = score / 10
var time = 10.00
var isTimerRunning = false
var isGameComplete = true
var realTime = 0.0
class ViewController: UIViewController {
#IBOutlet weak var resetButton: UIButton!
#IBOutlet weak var clickingButton: UIButton!
#IBOutlet weak var CPSLabelTxt: UILabel!
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var startButton: UIButton!
#IBOutlet weak var scoreLabelTxt: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
resetButton.isHidden = true
resetButton.isEnabled = false
CPSLabelTxt.isHidden = true
scoreLabelTxt.isHidden = false
scoreLabelTxt.text = "Press Start to begin"
}
#IBAction func resetButtonDidTap(_ sender: Any){
resetButton.isEnabled = false
startButton.isEnabled = true
time = 10.0
timerLabel.text = "10.00"
isTimerRunning = false
}
#IBAction func clickingButtonDidTap(_ sender: Any) {
if !isGameComplete {
score += 1
scoreLabelTxt.text = "\(score)"
}
}
//Start button is pressed
#IBAction func startButtonDidTap(_ sender: Any) {
isGameComplete = false
startButton.isEnabled = false
//Count down timer begins
if !isTimerRunning{
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true)
isTimerRunning = true
}
}
//logic for timer
#objc func runTimer(){
time -= 0.01
realTime = round(100 * time) / 100
timerLabel.text = "\(realTime)"
if time < 0.0 {
isTimerRunning = false
timerLabel.text = "0.00"
isGameComplete = true
resetButton.isHidden = false
CPSLabelTxt.isHidden = false
scoreLabelTxt.isHidden = false
scoreLabelTxt.text = "\(score)"
CPSLabelTxt.text = "\(cps)"
time = -2
}
}
}
Put timer.invalidate() in your resetButtonDidTap(_ sender: Any) function. This stops the timer from running.

How to make a stopwatch timer

For some reason the stopwatch looks to be alright, but when compared to the real deal it goes way too fast. I don't understand what I should do. Is it the timeInterval that I should change? I would like it to show millisecond, second and minutes, but everything I try just makes it worse.. Thank you
import UIKit
class ViewController: UIViewController {
// Outlets
#IBOutlet weak var minutes: UILabel!
#IBOutlet weak var seconds: UILabel!
#IBOutlet weak var milliSecondsLabel: UILabel!
#IBOutlet weak var resetButton: UIButton!
#IBOutlet weak var startButton: UIButton!
#IBOutlet weak var pauseButton: UIButton!
// Variables
var timer = Timer()
var time: Int = 0
var running: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
roundButtons()
}
#IBAction func resetTimer(_ sender: Any) {
timer.invalidate()
time = 0
updateUI()
running = false
}
#IBAction func startTimer(_ sender: Any) {
if running {
return
} else {
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(timerDidEnd), userInfo: nil, repeats: true)
running = true
}
}
#IBAction func pauseTimer(_ sender: Any) {
timer.invalidate()
running = false
}
func roundButtons() {
resetButton.layer.cornerRadius = resetButton.frame.height / 2
startButton.layer.cornerRadius = startButton.frame.height / 2
pauseButton.layer.cornerRadius = pauseButton.frame.height / 2
}
#objc func timerDidEnd() {
time += 1
updateUI()
}
func updateUI() {
var min: Int
var sec: Int
var mil: Int
min = time / (60*60)
sec = (time/60)%60
mil = time & 60
minutes.text = String(min)
seconds.text = String(sec)
milliSecondsLabel.text = String(mil)
}
}
I'd suggest two things:
Don’t try to count the time elapsed yourself. Capture the start time and calculate the elapsed time from that. You can use Date method timeIntervalSince, or, because that's not guaranteed to return monotonically increasing values, use CACurrentMediaTime, like below.
Rather than having a timer with an arbitrary 100 updates per second, instead use a CADisplayLink, which is optimally timed for device screen refresh rates.
E.g.:
class ViewController: UIViewController {
// Outlets
#IBOutlet weak var minutesLabel: UILabel!
#IBOutlet weak var secondsLabel: UILabel!
#IBOutlet weak var milliSecondsLabel: UILabel!
#IBOutlet weak var resetButton: UIButton!
#IBOutlet weak var startButton: UIButton!
#IBOutlet weak var pauseButton: UIButton!
// Variables
private weak var displayLink: CADisplayLink?
private var startTime: CFTimeInterval?
private var elapsed: CFTimeInterval = 0
private var priorElapsed: CFTimeInterval = 0
override func viewDidLoad() {
super.viewDidLoad()
setFonts()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
roundButtons()
}
#IBAction func resetTimer(_ sender: Any) {
stopDisplayLink()
elapsed = 0
priorElapsed = 0
updateUI()
}
#IBAction func startTimer(_ sender: Any) {
if displayLink == nil {
startDisplayLink()
}
}
#IBAction func pauseTimer(_ sender: Any) {
priorElapsed += elapsed
elapsed = 0
displayLink?.invalidate()
}
}
private extension ViewController {
func startDisplayLink() {
startTime = CACurrentMediaTime()
let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink.add(to: .main, forMode: .common)
self.displayLink = displayLink
}
func stopDisplayLink() {
displayLink?.invalidate()
}
#objc func handleDisplayLink(_ displayLink: CADisplayLink) {
guard let startTime = startTime else { return }
elapsed = CACurrentMediaTime() - startTime
updateUI()
}
func updateUI() {
let totalElapsed = elapsed + priorElapsed
let hundredths = Int((totalElapsed * 100).rounded())
let (minutes, hundredthsOfSeconds) = hundredths.quotientAndRemainder(dividingBy: 60 * 100)
let (seconds, milliseconds) = hundredthsOfSeconds.quotientAndRemainder(dividingBy: 100)
minutesLabel.text = String(minutes)
secondsLabel.text = String(format: "%02d", seconds)
milliSecondsLabel.text = String(format: "%02d", milliseconds)
}
func roundButtons() {
resetButton.layer.cornerRadius = resetButton.bounds.height / 2
startButton.layer.cornerRadius = startButton.bounds.height / 2
pauseButton.layer.cornerRadius = pauseButton.bounds.height / 2
}
func setFonts() {
minutesLabel.font = UIFont.monospacedDigitSystemFont(ofSize: minutesLabel.font.pointSize, weight: .regular)
secondsLabel.font = UIFont.monospacedDigitSystemFont(ofSize: secondsLabel.font.pointSize, weight: .regular)
milliSecondsLabel.font = UIFont.monospacedDigitSystemFont(ofSize: milliSecondsLabel.font.pointSize, weight: .regular)
}
}
That yields:
Completely unrelated, but anything dependent upon the size of views (e.g. the corner rounding) really belongs in viewDidLayoutSubviews, not viewDidLoad.

Swift Run An NSTimer Automatically

I'm making an app and using NSTimer to make the timer in my app.But I need the NSTimer to run when the scene start.I have two scene in my app a homescreen and the app it's self here is my code viewcontroller.swift (The second scene the first scene in empty) And btw it's my 3 day using swift and i'm in middle school ;).
import UIKit
var Number = 2
var Answer = Number * 2
var score = 0
var scorelabel = "Score: "
var Timer = NSTimer()
var Counter = 10
class SecondViewController: UIViewController {
#IBOutlet weak var RightAndWrongLabel: UILabel!
#IBOutlet weak var TimerLabel: UILabel!
#IBOutlet weak var RightAndWrong: UIImageView!
#IBOutlet weak var ScoreIabel: UILabel!
#IBOutlet weak var UserInputAnswer: UITextField!
#IBOutlet weak var Question: UILabel!
#IBOutlet weak var TimerOut: UIImageView!
var seconds = 0
var timeison = true
#IBAction func ConfirmAnswer(sender: AnyObject) {
let UserAnswer = Int(UserInputAnswer.text!)
if UserAnswer == Answer {
print("Your right")
Number += 2
score += 1
ScoreIabel.text = "Score: \(score)"
Question.text = "\(Number) x 2"
UserInputAnswer.text = ""
Answer = Number * 2
RightAndWrong.image = UIImage(named: "Label")
RightAndWrongLabel.hidden = false
RightAndWrongLabel.text = "Right!"
RightAndWrongLabel.textColor = UIColor(red: 0, green: 225, blue: 0, alpha: 1)
} else {
UserInputAnswer.text = ""
RightAndWrong.image = UIImage(named: "Label")
RightAndWrongLabel.hidden = false
RightAndWrongLabel.text = "Wrong!"
RightAndWrongLabel.textColor = UIColor(red: 225, green: 0, blue: 0, alpha: 1)
}
}
func DisplayTimer() {
Timer = NSTimer.scheduledTimerWithTimeInterval(1, target:self, selector: Selector("updateCounter"), userInfo: nil, repeats: true)
}
func updateTimer(){
TimerLabel.text = String(Counter--)
}
override func viewDidLoad() {
super.viewDidLoad()
UserInputAnswer.keyboardType = UIKeyboardType.NumberPad
RightAndWrongLabel.hidden = true
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
3
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Swift 2.x :
func DisplayTimer() {
Timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target:self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
}
func updateTimer() {
if Counter != 0 { TimerLabel.text = "\(Counter -= 1)"
} else {
Timer.invalidate()
// call a game over method here...
}
}
override func viewDidLoad() {
super.viewDidLoad()
...
// start the timer when this controller shows up
DisplayTimer()
TimerLabel.text = "\(Counter)"
...
}
If you want to start your timer when the scene loads call DisplayTimer() under
override func viewDidLoad() when the view is loaded
Also if you want to update your label every second, the selector in your NSTimer is calling updateCounter not updateTimer

Segmented Control in Calculator

Im trying to make a Segmented Control that holds two values, but I'm having trouble making it. I want to multiply the input by whatever side is picked in the segmented view and displayed in the label, but I keep running into errors. Any help?
#IBOutlet var input: UITextField!
#IBOutlet var output: UILabel!
#IBOutlet var controller: UISegmentedControl!
#IBAction func convert(sender: UIButton) {
}
#IBAction func change(sender: AnyObject) {
if controller.selectedSegmentIndex == 0 {
result = number1 * 1
}
if controller.selectedSegmentIndex == 1 {
result = number1 * 5
}
let number1 = Double(input.text!)
label.text = "\(result)"
}
Why you use "number1" before declaration?
Try this:
#IBOutlet var input: UITextField!
#IBOutlet var output: UILabel!
var result:Double! //fixed "cannot be applied to Double and Int." problem
#IBOutlet var controller: UISegmentedControl!
#IBAction func convert(sender: UIButton) {
}
#IBAction func change(sender: AnyObject) {
let number1 = Double(input.text!)
if controller.selectedSegmentIndex == 0 {
self.result = number1 * 1
}
if controller.selectedSegmentIndex == 1 {
self.result = number1 * 5
}
label.text = "\(result)"
}

Resources