skip to Main Content

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

2

Answers


  1. 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)
    
    Login or Signup to reply.
  2. You error message: // Error: Cannot find 'breathCounter' in scope gives a good clue… it’s all about scope.

    You declare your UILabel within the ViewDidLoad() method and so that’s where it lives; that’s its scope. As soon as ViewDidLoad completes, *poof * UILabel disappears from memory.

    What you need to do is move your let breathCounter = UILabel() outside of ViewDidLoad so it gets created along with your ViewController; then you will be able to reference it as long as your ViewController exists in memory.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search