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

2.5k views Asked by At

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?

3

There are 3 answers

1
Bilal On BEST ANSWER

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        
}
2
Muhammad Waqas Bhati On

You have to change TextColor in button Click Method

@IBAction func btnWhite(_ sender: UIButton) {
     myCustomLabel.textColor = UIColor.blue
} 

and no need to call the below mentioned methods

self.view.setNeedsDisplay()
self.view.layoutIfNeeded()

Note: There is no other way to change UIlabel TextColor

0
Joel Prine On

Bilal's Answer is correct. Sometimes a completely different approach is what is needed. Thanks for the fresh set of eyes, here is the final code.

Add the code below and set all your labels to this "MyLabel" class. Then anytime the GlobalVars.mainTextColor variable is changed, the labels update their text color. This can be adapted for UIButtons or really any property you need to update when a var changes. This updates any labels with this class on any VC that is loaded. This also fixes my problem of the root VC labels also needing updated, since they were also already drawn with the original color, very slick!

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

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

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