4

Latest XCode. I have a project in which in any storyboards/view controller setting properties such as textColor of a UILabel or backgroundColor of a UIButton in viewDidLoad doesn't work but works in viewDidAppear.

What kills me is that I can't reproduce it in a new/different simple project.

Anyone has any idea what could possibly be wrong? It's a big project. Would be a major mess to recreate it. I have a feeling that this is unresolvable. Please prove me wrong.

Sorry not much code to show besides:

@IBOutlet weak var theTextField: UITextField!
@IBOutlet weak var theLabel: UILabel!
@IBOutlet weak var theButton: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()
    theTextField.text = "some text"      //<<<- works
    theTextField.textColor = UIColor.red //<<<- works
    theLabel.textColor = UIColor.red     //<<<- **doesn't work**
    theButton.backgroundColor = UIColor.red //<<<- **doesn't work**
    theButton.setTitleColor(UIColor.blue, for: UIControl.State.normal) //<<<- works

}
override func **viewDidAppear**(_ animated: Bool) {
    super.viewDidAppear(animated)
    theTextField.text = "some text"         //<<<- works
    theTextField.textColor = UIColor.red    //<<<- works
    theLabel.textColor = UIColor.red        //<<<- **works**
    theButton.backgroundColor = UIColor.red //<<<- **works**
    theButton.setTitleColor(UIColor.blue, for: UIControl.State.normal) //<<<- works
}

Here theTextField is updated fine in all cases, but a label and a button won't update in 'viewDidLoad'.

EDIT: updated code to show that some things work and other don't

EDIT 2: problem with using viewDidAppear is that the screen shows up with 1 set of colors (specified in designer) but then all colors switch in front of the user - which looks ugly. And any kind of delays won't help it. The goal is to "prepare" the screen with all colors and such before it is animated into view. And these colors depend on some dynamic data and can't be specified in the designer.

EDIT 3: I just figured that doing all of this in viewDidLayoutSubviews gets me what I want:

@IBOutlet weak var theTextField: UITextField!
@IBOutlet weak var theLabel: UILabel!
@IBOutlet weak var theButton: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()
    theTextField.text = "some text"      //<<<- works
    theTextField.textColor = UIColor.red //<<<- works
    theLabel.textColor = UIColor.red     //<<<- doesn't work
    theButton.backgroundColor = UIColor.red //<<<- doesn't work
    theButton.setTitleColor(UIColor.blue, for: UIControl.State.normal) //<<<- works

}
override func viewDidLayoutSubviews() {
    super.viewDidAppear()
    theTextField.text = "some text"         //<<<- works
    theTextField.textColor = UIColor.red    //<<<- works
    theLabel.textColor = UIColor.red        //<<<- works
    theButton.backgroundColor = UIColor.red //<<<- works
    theButton.setTitleColor(UIColor.blue, for: UIControl.State.normal) //<<<- works
}

Seems like a possible workaround. But why? Why do I need to resort to a workaround? I really would like to understand why viewDidLoad doesn't work in my this particular case/project. Or is this not a workaround and a proper way to do this? Everywhere I've seen in all sample code everybody always does all this sort of initialization in viewDidLoad. I've never seen anyone doing it in viewDidLayoutSubviews or viewDidAppear. Have I not looked long enough? What am I missing?

  • That's strange !! , try to do it in main thread and add self.view.layoutIfNeeded() – Prashant Tukadiya Nov 29 '18 at 04:50
  • 1
    Any updates to _User Interface Elements_ should be done in `viewDidAppear()` and not `viewDidLoad()` as you have no guarantee whether _views_ are loaded while we are in `viewDidLoad` and if not, you cannot apply customisations to it. However, in `viewDidAppear` , you can guarantee that all the _view_ have been completely loaded . – Shubham Bakshi Nov 29 '18 at 04:51
  • Search for `theLabel.textColor` in your ViewController and check is there any other place you are assigning text color to theLabel. – Natarajan Nov 29 '18 at 04:57
  • @Natarajan: no, that's just the thing - just to test this I add new label to one of my soryboards to avoid a prior use and the same thing happens. All views fail. – StronkStinq Nov 29 '18 at 05:12
  • @user3403344 if you don't set textColor in viewDidAppear, what's the actual textColor of your `theLabel `? is it showing black text? – Natarajan Nov 29 '18 at 05:19
  • no, I set it to some color in the designer - that's the color it shows up with – StronkStinq Nov 29 '18 at 05:31
  • 1
    Shubham Bakshi is wrong. ViewDidLoad is exactly the place where all of your outlets are first guaranteed to be set (init and loadView run first). Your code should work as written, but if not try calling setNeedsDisplay on the label to force it to redraw itself with the correct color. – Josh Homann Nov 29 '18 at 09:22
  • @Josh Homann, setNeedsDisplay has no effect. It's as if properties from designer are applied after viewDidLoad. What pisses the heck out of me that , like I said, I started a new project and and that one everything works fine. I just don't want to go through recreating the app. – StronkStinq Nov 29 '18 at 13:20

4 Answers4

0

Nothing is wrong. It rendered the label before executing the code. Try the below code it'll work

@IBOutlet weak var theLabel: UILabel!
override func viewDidLoad() {
    super.viewDidLoad()
    DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4), execute: {
        theLabel.textColor = UIColor.red
    })
}
Malik
  • 3,763
  • 1
  • 22
  • 35
Manish_Nainwal
  • 301
  • 1
  • 5
  • Dont put delay on ViewDidLoad – Kathiresan Murugan Nov 29 '18 at 05:32
  • I don't get it. "rendered the label before executing the code"? And how is updating properties in DispatchQueue.main with a delay better than just doing it in viewDidAppear? Same problem: the screen shows up with some initial colors and then switches in front of the user which is ugly. My goal is to "prepare" the screen before it's animated into view. – StronkStinq Nov 29 '18 at 05:46
  • in the above code i am just trying to explain the execution its not the solution , ViewDidAppear is called everytime when you see the view after it is loaded thats why the code is working in viewDidAppear. – Manish_Nainwal Nov 29 '18 at 06:04
  • right, I understand why it works in viewDidAppear. What I'm struggling to understand why **some** of this doesn't work in viewDidLoad. Everywhere I ever saw any sample code all initialization of this sort of stuff happens in viewDidLoad. I never seen anyone use viewDidAppear. – StronkStinq Nov 29 '18 at 14:02
0

Setting label color to Default solved same problem for me.

enter image description here

llkenny
  • 146
  • 1
  • 6
0

viewWillAppear is a better place than viewDidAppear.

viewDidLoad should work. Did you set a breakpoint and check that your code is actually called? It wouldn't be called if a subclass doesn't call super.viewDidLoad().

And did you check that theLabel and theButton are not nil? Did you check your code to make sure theLabel and theButton are not set elsewhere?

gnasher729
  • 51,477
  • 5
  • 75
  • 98
0

I also see this issue in iOS 12. The error only occurs when you assign a color through the Storyboard. If you use the default color on the Storyboard, then everything works as expected. This is an iOS bug that was fixed in version 13.

I found several ways to solve this problem:

  1. The best option! Do not change the default color on Storyboard (leave the default color), but assign it directly in code. In this case, you can assign a color, including in viewDidLoad
  2. You can wrap the code in DispatchQueue.main.async {} and still use it in viewDidLoad
  3. You can transfer the code to viewDidAppear, but in this case, the view may blink. Moving the code to viewWillAppear doesn't solve the problem!
Vergiliy
  • 1,248
  • 1
  • 13
  • 22