I´m trying to update my #IBOutlet weak var gameclockLabel: UILabel! from my class Gameclock with delegate.
I have read and tested about a million different ways but can't make it work. I think that the more I read about it the more confused I get.
You can read more about what I'm trying to do here:
swift invalidate timer in function
From the answers in that question I added this: var gameClock = Gameclock() so I was able to start a function in class Gameclock and first I tried to do the same with my class ViewController: UIViewController but that didn't work so that's why I decided to try with delegate instead. Do you think delegate is the right way to go with this?
I'm going to add several timers in separate classes to this later on so perhaps there's a better way.
Would be nice if someone could point me in the right direction. At first I thought this would be not to complicated but seems I was mistaking :)
The complete code is as follows:
import UIKit
protocol test1: class {
func updateLabel()
}
class ViewController: UIViewController, test1 {
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidLoad() {
super.viewDidLoad()
gameclockLabel.text = "00:00"
}
var gameClock = Gameclock()
var startstopPushed: Bool = false
#IBOutlet weak var gameclockLabel: UILabel!
#IBOutlet weak var startstop: UIButton!
#IBAction func startStopbutton(sender: AnyObject) {
if startstopPushed == false {
gameClock.startGameclock()
startstop.setImage(UIImage(named: "stop.png"), forState: UIControlState.Normal)
startstopPushed = true
}
else
{
gameClock.stopGameclock()
startstop.setImage(UIImage(named: "start.png"), forState: UIControlState.Normal)
startstopPushed = false
}
}
func updateLabel() {
print("updated")
gameclockLabel.text = gameClock.timeString
}
}
class Gameclock : NSObject {
var gameclockTimer = NSTimer()
var timeString: String = ""
var seconds = 0
var minutes = 0
weak var delegate: test1?
func startGameclock() {
print("start")
gameclockTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateGameclock"), userInfo: nil, repeats: true)
}
func stopGameclock() {
self.gameclockTimer.invalidate()
print("stopp")
}
func updateGameclock() {
seconds += 1
if seconds == 60 {
minutes += 1
seconds = 0
}
let secondsString = seconds > 9 ? "\(seconds)" : "0\(seconds)"
let minutesString = minutes > 9 ? "\(minutes)" : "0\(minutes)"
timeString = "\(minutesString):\(secondsString)"
print(timeString)
delegate?.updateLabel()
}
}
You haven't actually set your ViewController instance as your GameClocks delegate, so your updateLabel method won't be called;
override func viewDidLoad() {
super.viewDidLoad()
gameclockLabel.text = "00:00"
self.gameClock.delegate=self
}
Related
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.
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.
I have a label. And I have 3 strings. I need to display text of 3 strings in the same label with delay of 10 seconds over a infinite loop. How can i solve this with simple animations in swift 3?
That's my solution. Just connect a UILabel to the IBOutlet
class ViewController: UIViewController {
#IBOutlet weak var textLabel: UILabel!
let messages = ["PROFESSIONAL AND BEST LEARNING CENTER","LEARNING TECHNOLOGY AND DESIGN IN A SMART WAY","EXPLORE YOUR SKILLS"]
let delayTime = 10.0
var counter = 0
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(timeInterval: delayTime, target: self, selector: #selector(changeDisplayedText), userInfo: nil, repeats: true)
timer.fire()
}
func changeDisplayedText() {
textLabel.text = messages[counter % messages.count]
counter += 1
}
}
This will work for your, connect your outlet properly and declare those string in an array and load it with timer change.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var array = ["Aaaaaaaaaaa", "Bbbbbbbbbbb", "Ccccccccccc"]
var scrollIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(self.myDelayedFunction), userInfo: nil, repeats: true)
timer.fire()
}
func myDelayedFunction()-> Void {
let count = self.array.count
if scrollIndex == count {
scrollIndex = 0
}
if scrollIndex < count {
if count > 1{
self.label.text = array[scrollIndex]
self.scrollIndex += 1
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
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
I´m trying to create a hockey game clock-app with a main game clock and a start/stop-button. But I´m having trouble with my stopGameclock function. The timer won't invalidate. From searching other questions here I think it has to do with my:
var gameclockTimer = NSTimer()
Seams I can't use this var to invalidate the timer.
I know the function works because I can see "stop"
func stopGameclock() {
self.gameclockTimer.invalidate()
print("stop")
Is there any way to make make this function invalidate my timer?
Unfortunately the answers I found so far haven't helped me without the need to put the timer and functions in the ViewController class.
The reason I want to keep the timer in a separate class is because later on I will add several penalty clocks/timers and I want to have them in separate classes to keep it easy to overview.
The complete code looks like this so far:
// ViewController.swift
import UIKit
class ViewController: UIViewController {
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidLoad() {
super.viewDidLoad()
gameclockLabel.text = "00:00"
}
var startstopPushed: Bool = false
#IBOutlet weak var gameclockLabel: UILabel!
#IBOutlet weak var startstop: UIButton!
#IBAction func startStopbutton(sender: AnyObject) {
if startstopPushed == false {
Gameclock().startGameclock()
startstop.setImage(UIImage(named: "stop.png"), forState: UIControlState.Normal)
startstopPushed = true
}
else
{
Gameclock().stopGameclock()
startstop.setImage(UIImage(named: "start.png"), forState: UIControlState.Normal)
startstopPushed = false
}
}
}
class Gameclock : NSObject {
var gameclockTimer = NSTimer()
var timeString: String = ""
var seconds = 0
var minutes = 0
func startGameclock() {
gameclockTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateGameclock"), userInfo: nil, repeats: true)
}
func stopGameclock() {
self.gameclockTimer.invalidate()
print("stop")
}
func updateGameclock() {
seconds += 1
if seconds == 60 {
minutes += 1
seconds = 0
}
let secondsString = seconds > 9 ? "\(seconds)" : "0\(seconds)"
let minutesString = minutes > 9 ? "\(minutes)" : "0\(minutes)"
timeString = "\(minutesString):\(secondsString)"
print(timeString)
}
}
the problem is that each time you're accessing GameClock, you are creating a new instance of it - so the instance that you're stopping is not the one that you created.
You can keep all of the functionality in the GameClock class, but you will need to define a variable in ViewController to access it.
class ViewController: UIViewController {
var gameClock = Gameclock()
and then in your startstopPushed method make these changes
if startstopPushed == false {
//Gameclock().startGameclock()
gameClock.startGameclock()
startstop.setImage(UIImage(named: "stop.png"), forState: UIControlState.Normal)
startstopPushed = true
}
else
{
//Gameclock().stopGameclock()
gameClock.stopGameclock()
startstop.setImage(UIImage(named: "start.png"), forState: UIControlState.Normal)
startstopPushed = false
}
I think this should work:
I create a var gameClock just once which represents your game clock class and then run the methods on this one class
override func viewDidLoad() {
super.viewDidLoad()
gameclockLabel.text = "00:00"
}
var startstopPushed: Bool = false
var gameClock = GameClock()
#IBOutlet weak var gameclockLabel: UILabel!
#IBOutlet weak var startstop: UIButton!
#IBAction func startStopbutton(sender: AnyObject) {
if startstopPushed == false {
gameClock.startGameclock()
startstop.setImage(UIImage(named: "stop.png"), forState: UIControlState.Normal)
startstopPushed = true
}
else
{
gameClock.stopGameclock()
startstop.setImage(UIImage(named: "start.png"), forState: UIControlState.Normal)
startstopPushed = false
}
}