swiftuilabelredrawsetneedsdisplay

Swift 4 - setNeedsDisplay and layoutIfNeeded not redrawing UILabel on self.UIView


Basically, I have a custom UILabel subclass that sets the label colors based on a variable.

class MyLabel: UILabel {
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        textColor = GlobalVars.mainTextColor
    }
}

On my view controller I have a button that sets a white background and sets the text color variable to black:

@IBAction func btnWhite(_ sender: UIButton) {
        GlobalVars.mainTextColor = UIColor.black
        GlobalVars.bgColor = UIColor.white
        self.view.backgroundColor = UIColor.white
        self.view.setNeedsDisplay()
        self.view.layoutIfNeeded()
        DispatchQueue.main.async { [weak self] in
            self?.view.setNeedsLayout()
            self?.view.layoutIfNeeded()
        }
    }

Once this is clicked and the variables updated, I want the ViewController to redraw the labels, which would update their text colors. This work fine if I pop the VC and come back, but I want the display to update when the buttons is clicked. I have a bunch of labels on the view and thy are all set to myLabel class. I do not want to have to manually code changes to every label on the screen, I just want to redraw it. The UIView is not a custom class, just the default UIView that comes with a View Controller. This should be occurring on the main thread already, but I have tried adding the dispatch.main.async just in case. I expect it would not need both in the end. Image of my view controller layout here

When I click the button, the background changes to white, but the label text colors do not update, I believe that is because it is not redrawing them. There is a second button btnBlack, that toggles it the exact opposite for a light/dark mode effect.

Thoughts?


Solution

  • Whenever GlobalVars.mainTextColor or GlobalVars.bgColor values change, you have to set the colors of all your labels again.

    One option is to use Notifications. You can post a notification on didSet of your global variables and catch the notification in your MyLabel class and update the colors.

    class GlobalVars {
        static let onChangeColorNotification = "onChangeColorNotification"
        static var mainTextColor:UIColor? {
            didSet {
                NotificationCenter.default.post(Notification.init(name: Notification.Name(rawValue: onChangeColorNotification)))
            }
        }
    }
    

    And in your MyLabel class

    class MyLabel: UILabel {
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            onChangeGlogabColor()
            NotificationCenter.default.addObserver(self, selector: #selector(onChangeGlogabColor), name: NSNotification.Name(rawValue: GlobalVars.onChangeColorNotification), object: nil)
        }
        @objc func onChangeGlogabColor() {
            textColor = GlobalVars.mainTextColor
        }
    }
    

    You can do the same with all your custom classes like MyButton or MyView, catch the notification and update the colors.

    And you don't have to call setNeedsDisplay() or layoutIfNeeded().

    @IBAction func btnWhite(_ sender: UIButton) {
        GlobalVars.mainTextColor = UIColor.black
        GlobalVars.bgColor = UIColor.white
        self.view.backgroundColor = UIColor.white        
    }