EtherCode EtherCode - 3 months ago 13
Swift Question

UILabel doesn't seem to update as time data changes

I've spent the last couple of months learning swift and thought I'd have a go at making a multi timer, please forgive any rookie errors! I have created a timer class which receives the input data from the user and handles the countdown. This all seems to be working fine however the label which displays the time remaining only ever updates when I click the start button.
The code for my timer class is as follows:

class TimerClass: NSObject {

var timer = NSTimer ()
var startTimeCounter:NSTimeInterval = 0
var hours = Int?()
var minutes = Int?()
var seconds = Int?()
var timeString = String()
var firstCheck:Bool = true

func getTime() {
// check if user has already clicked start so as to not double up the decrement
if firstCheck == true {
startTimeCounter += (NSTimeInterval(hours!) * 3600)
startTimeCounter += (NSTimeInterval(minutes!) * 60)
startTimeCounter += NSTimeInterval(seconds!)
firstCheck = false
}
else {
firstCheck = false
}
// turn the user data into hrs mins secs
var timeRemaining : NSTimeInterval = startTimeCounter

var hoursInt = Int(timeRemaining / 3600.0)
timeRemaining -= (NSTimeInterval(hoursInt) * 3600)

var minutesInt = Int(timeRemaining / 60.0)
timeRemaining -= (NSTimeInterval(minutesInt) * 60)

var secondsInt = Int(timeRemaining)
timeRemaining -= NSTimeInterval(secondsInt)

var strHours = String(format: "%02d", hoursInt)
var strMinutes = String(format: "%02d", minutesInt)
var strSeconds = String(format: "%02d", secondsInt)
// create a string for the label
timeString = "\(strHours):\(strMinutes):\(strSeconds)"
}

func updateTime () {
startTimeCounter -= 1
getTime()
}

func startTimer() {
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector:#selector(TimerClass.updateTime), userInfo: nil, repeats: true)

}


}


Also my view controller looks like this:

class ViewController: UIViewController {


@IBOutlet weak var timeRemainingLabel: UILabel!
@IBOutlet weak var startButton: UIButton!
@IBOutlet weak var setDoneButton: UIButton!
@IBOutlet weak var secStepper: UIStepper!
@IBOutlet weak var minStepper: UIStepper!
@IBOutlet weak var hourStepper: UIStepper!
@IBOutlet weak var secLabel: UILabel!
@IBOutlet weak var minLabel: UILabel!
@IBOutlet weak var hourLabel: UILabel!
@IBOutlet weak var setView: UIView!
@IBOutlet weak var timerLabel: UILabel!
@IBOutlet weak var setButton: UIButton!
@IBOutlet weak var pauseButton: UIButton!


var hourSelected = Int()
var minSelected = Int()
var secSelected = Int()
var newTimer:TimerClass = TimerClass()
var startPressed:Bool = false



override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.


}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()

// Dispose of any resources that can be recreated.
}
// Set button pressed
@IBAction func setPressed(sender: AnyObject) {
self.setView.alpha = 0.8


}
// User closed set menu
@IBAction func setDonePressed(sender: AnyObject) {
self.setView.alpha = 0
newTimer.hours = Int(self.hourStepper.value)
newTimer.minutes = Int(self.minStepper.value)
newTimer.seconds = Int(self.secStepper.value)
newTimer.getTime()



}
// Stepper functions
@IBAction func hourStepperPressed(sender: AnyObject) {
self.hourLabel.text = String(format: "%.0f", hourStepper.value)

}
@IBAction func minStepperPressed(sender: AnyObject) {
self.minLabel.text = String(format: "%.0f", minStepper.value)
}
@IBAction func secStepperPressed(sender: AnyObject) {
self.secLabel.text = String(format: "%.0f", secStepper.value)

}

// User pressed start
@IBAction func startPressed(sender: AnyObject) {
self.newTimer.startTimer()
updateTimerLabel()
startPressed = true
}

func updateTimerLabel () {
timeRemainingLabel.text = newTimer.timeString


}





}


I appreciate that I have probably gone the long way around this problem but it is my first independent app so I'm still getting my head around things!

Answer

You're only updating your UI when the user selects the done button.

One way to not have another timer is for your TimerClass to tell your view controller that it needs to update the UI.

You can create a protocol in your TimerClass, and set your view controller as the timers 'delegate' object.

Something like this:

protocol TimerDelegate: class {
    /**
    * Tell the delegate to update the interface
    **/
    func updateUI()
}

class TimerClass: NSObject {  
    var timer = NSTimer ()
    ...
    var firstCheck:Bool = true

    weak var delegate: TimerDelegate?

    func getTime() {
        ///
    }

    func updateTime () {
        startTimeCounter -= 1
        getTime()
        delegate?.updateUI()
    }

    func startTimer() {
        ...
    }
}

And then your view controller

class ViewController: UIViewController, TimerDelegate
{
    @IBOutlet weak var timeRemainingLabel: UILabel!
    ...
    @IBOutlet weak var pauseButton: UIButton!

    var hourSelected = Int()
    ...
    var startPressed:Bool = false

    override func viewDidLoad() {
        super.viewDidLoad()
        newTimer.delegate = self
    }

    // Set button pressed
    @IBAction func setPressed(sender: AnyObject) {
        ...
    }

    // User closed set menu
    @IBAction func setDonePressed(sender: AnyObject) {
        ...
    }

    // Stepper functions
    @IBAction func hourStepperPressed(sender: AnyObject) {
        ...

    }
    @IBAction func minStepperPressed(sender: AnyObject) {
        ...
    }
    @IBAction func secStepperPressed(sender: AnyObject) {
        ...
    }

    //MARK: TimerDelegate

    func updateUI() {
        updateTimerLabel()
    }

    // User pressed start
    @IBAction func startPressed(sender: AnyObject) {
        self.newTimer.startTimer()
        updateTimerLabel()
        startPressed = true
    }

    func updateTimerLabel () {
        timeRemainingLabel.text = newTimer.timeString
    }
}
Comments