Search code examples
iosswiftxcodeswift5didset

What is the proper way to update my label's text from a function?


I am trying to have my UILabel's text automatically update in increments of 1 based on a looping Timer. The label is linked to a variable. I'm not sure if it matters but I am doing my UI programmatically using auto layout anchors.

I understand this is not working because the variable lives outside of ViewDidLoad(). I also tried setting up a subclass of a UILabel in a separate file but I could not figure out the proper way to do that. I was having trouble connecting the variable to the subclass and properly implementing didSet.

Here is the relevant code from my View Controller, any recommendations or alternative methods are appreciated.

import UIKit

class ViewController: UIViewController {

    var numberOfBreaths = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .white
        
        let breathCounter = UILabel()
        breathCounter.translatesAutoresizingMaskIntoConstraints = false
        breathCounter.text = "\(numberOfBreaths)"
        breathCounter.textAlignment = .center
        breathCounter.center = self.view.center

        // Irrelevant hidden label code redacted

        let startStop = RoundButton()
        startStop.translatesAutoresizingMaskIntoConstraints = false
        startStop.backgroundColor = .white
        startStop.setTitle("breathe", for: .normal)
        startStop.setTitleColor(.darkGray , for: .normal)
        startStop.layer.borderWidth = 2.5
        startStop.layer.borderColor = CGColor(red: 225, green: 225, blue: 0, alpha: 1)
        startStop.addTarget(self, action: #selector(self.breathCount), for: .touchUpInside)

        view.addSubview(breathCounter)
        view.addSubview(holdTimer)
        view.addSubview(startStop)

        // Anchor code redacted
    }

    @objc func breathCount(_ sender: RoundButton) {
        print("Button Tapped")
        createTimer()
    }
    
    func createTimer() {
        _ = Timer.scheduledTimer(timeInterval: 3.5, target: self, selector: #selector(nextBreath), userInfo: nil, repeats: true)
    }

    @objc func nextBreath() {
        numberOfBreaths += 1
        breathCounter.text = "\(numberOfBreaths)" // Error: Cannot find 'breathCounter' in scope
        print(numberOfBreaths) // Prints expected number to console
    }

}

View for context


Solution

  • If you declare breathCounter as a property on your view controller (like you did for numberOfBreaths, you will have access to it from both the viewDidLoad and nextBreath functions. I'd also hold a reference to your Timer

    class ViewController: UIViewController {
    
        var numberOfBreaths = 0
        let breathCounter = UILabel()
        var timer : Timer?
    

    And then inside viewDidLoad, remove the existing let breathCounter = UILabel() line.

    And inside createTimer:

    self.timer = Timer.scheduledTimer(timeInterval: 3.5, target: self, selector: #selector(nextBreath), userInfo: nil, repeats: true)