named colors get set after viewDidLoad() WHY?!

When using named colors in your interface builder files their values get set AFTER viewDidLoad() and awakeFromNib() get called. Making it difficult 😠 to change the values at run time. Is this by design? is their some other method i need to override so I can change the colors at run time? Should I just stop using named colors?


In my test project I have a view and I set the background to a named color then try and set that background to somthing else in viewDidLoad(). If I don't use a named color things work as you would expect. This app only supports iOS 11+


I made a custom view called MyView and log the back trace when the background is set. you see it first set when the control is being created in initWithCoder


(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x000000010eba36b2 ***`MyView.backgroundColor.didset(oldValue=nil, self=0x00007fb97e707eb0) at MyView.swift:17
    frame #1: 0x000000010eba3674 ***`MyView.backgroundColor.setter(newValue=0x000060000006f640, self=0x00007fb97e707eb0) at MyView.swift:0
    frame #2: 0x000000010eba35ac ***`@objc MyView.backgroundColor.setter at MyView.swift:0
    frame #3: 0x000000010fde7ecc UIKit`-[UIView initWithCoder:] + 1242
    frame #4: 0x000000010eba3a1a ***`MyView.init(aDecoder=0x00007fb98000a600) at MyView.swift:0
    frame #5: 0x000000010eba3abf ***`@objc MyView.init(coder:) at MyView.swift:0
    frame #6: 0x00000001101d4725 UIKit`-[UIClassSwapper initWithCoder:] + 246
    frame #7: 0x000000011042688c UIKit`UINibDecoderDecodeObjectForValue + 704
    frame #8: 0x00000001104265bf UIKit`-[UINibDecoder decodeObjectForKey:] + 246
    frame #9: 0x00000001101d43f1 UIKit`-[UIRuntimeConnection initWithCoder:] + 178
    frame #10: 0x000000011042688c UIKit`UINibDecoderDecodeObjectForValue + 704
    frame #11: 0x0000000110426a2a UIKit`UINibDecoderDecodeObjectForValue + 1118
    frame #12: 0x00000001104265bf UIKit`-[UINibDecoder decodeObjectForKey:] + 246
    frame #13: 0x00000001101d35e2 UIKit`-[UINib instantiateWithOwner:options:] + 1262
    frame #14: 0x000000010feee0d7 UIKit`-[UIViewController _loadViewFromNibNamed:bundle:] + 383
    frame #15: 0x000000010feeea04 UIKit`-[UIViewController loadView] + 177
    frame #16: 0x000000010feeed21 UIKit`-[UIViewController loadViewIfRequired] + 175
    frame #17: 0x000000010feef574 UIKit`-[UIViewController view] + 27
    frame #18: 0x000000010fdbd123 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 122
    frame #19: 0x000000010fdbd834 UIKit`-[UIWindow _setHidden:forced:] + 294
    frame #20: 0x000000010fdd05cc UIKit`-[UIWindow makeKeyAndVisible] + 42
    frame #21: 0x000000010fd443da UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4739
    frame #22: 0x000000010fd495cb UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1677
    frame #23: 0x000000011010bf7e UIKit`__111-[__UICanvasLifecycleMonitor_Compatability _scheduleFirstCommitForScene:transition:firstActivation:completion:]_block_invoke + 866
    frame #24: 0x00000001104dea39 UIKit`+[_UICanvas _enqueuePostSettingUpdateTransactionBlock:] + 153
    frame #25: 0x000000011010bbba UIKit`-[__UICanvasLifecycleMonitor_Compatability _scheduleFirstCommitForScene:transition:firstActivation:completion:] + 236
    frame #26: 0x000000011010c3db UIKit`-[__UICanvasLifecycleMonitor_Compatability activateEventsOnly:withContext:completion:] + 675
    frame #27: 0x0000000110a7d614 UIKit`__82-[_UIApplicationCanvas _transitionLifecycleStateWithTransitionContext:completion:]_block_invoke + 299
    frame #28: 0x0000000110a7d4ae UIKit`-[_UIApplicationCanvas _transitionLifecycleStateWithTransitionContext:completion:] + 433
    frame #29: 0x000000011076175d UIKit`__125-[_UICanvasLifecycleSettingsDiffAction performActionsForCanvas:withUpdatedScene:settingsDiff:fromSettings:transitionContext:]_block_invoke + 221
    frame #30: 0x000000011095c4b7 UIKit`_performActionsWithDelayForTransitionContext + 100
    frame #31: 0x0000000110761627 UIKit`-[_UICanvasLifecycleSettingsDiffAction performActionsForCanvas:withUpdatedScene:settingsDiff:fromSettings:transitionContext:] + 223
    frame #32: 0x00000001104de0e0 UIKit`-[_UICanvas scene:didUpdateWithDiff:transitionContext:completion:] + 392
    frame #33: 0x000000010fd47eac UIKit`-[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 515
    frame #34: 0x000000011031abcb UIKit`-[UIApplicationSceneClientAgent scene:didInitializeWithEvent:completion:] + 361
    frame #35: 0x00000001150d72f3 FrontBoardServices`-[FBSSceneImpl _didCreateWithTransitionContext:completion:] + 331
    frame #36: 0x00000001150dfcfa FrontBoardServices`__56-[FBSWorkspace client:handleCreateScene:withCompletion:]_block_invoke_2 + 225
    frame #37: 0x000000011420c7ec libdispatch.dylib`_dispatch_client_callout + 8
    frame #38: 0x0000000114211db8 libdispatch.dylib`_dispatch_block_invoke_direct + 592
    frame #39: 0x000000011510b470 FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
    frame #40: 0x000000011510b12e FrontBoardServices`-[FBSSerialQueue _performNext] + 439
    frame #41: 0x000000011510b68e FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 45
    frame #42: 0x000000011304abb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #43: 0x000000011302f4af CoreFoundation`__CFRunLoopDoSources0 + 271
    frame #44: 0x000000011302ea6f CoreFoundation`__CFRunLoopRun + 1263
    frame #45: 0x000000011302e30b CoreFoundation`CFRunLoopRunSpecific + 635
    frame #46: 0x00000001159a0a73 GraphicsServices`GSEventRunModal + 62
    frame #47: 0x000000010fd4b057 UIKit`UIApplicationMain + 159
    frame #48: 0x000000010eba5a67 ***`main at AppDelegate.swift:12
    frame #49: 0x0000000114289955 libdyld.dylib`start + 1
    frame #50: 0x0000000114289955 libdyld.dylib`start + 1
(lldb)


Then in my viewDidLoad() method I set it


(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x000000010eba36b2 ***`MyView.backgroundColor.didset(oldValue=0x0000608000271d40, self=0x00007fb97e707eb0) at MyView.swift:17
    frame #1: 0x000000010eba3674 ***`MyView.backgroundColor.setter(newValue=0x0000608000272000, self=0x00007fb97e707eb0) at MyView.swift:0
    frame #2: 0x000000010eba35ac ***`@objc MyView.backgroundColor.setter at MyView.swift:0
    frame #3: 0x000000010eba42b7 ***`ViewController.viewDidLoad(self=0x00007fb981504fa0) at ViewController.swift:20
    frame #4: 0x000000010eba4484 ***`@objc ViewController.viewDidLoad() at ViewController.swift:0
    frame #5: 0x000000010feef131 UIKit`-[UIViewController loadViewIfRequired] + 1215
    frame #6: 0x000000010feef574 UIKit`-[UIViewController view] + 27
    frame #7: 0x000000010fdbd123 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 122
    frame #8: 0x000000010fdbd834 UIKit`-[UIWindow _setHidden:forced:] + 294
    frame #9: 0x000000010fdd05cc UIKit`-[UIWindow makeKeyAndVisible] + 42
    frame #10: 0x000000010fd443da UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4739
    frame #11: 0x000000010fd495cb UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1677
    frame #12: 0x000000011010bf7e UIKit`__111-[__UICanvasLifecycleMonitor_Compatability _scheduleFirstCommitForScene:transition:firstActivation:completion:]_block_invoke + 866
    frame #13: 0x00000001104dea39 UIKit`+[_UICanvas _enqueuePostSettingUpdateTransactionBlock:] + 153
    frame #14: 0x000000011010bbba UIKit`-[__UICanvasLifecycleMonitor_Compatability _scheduleFirstCommitForScene:transition:firstActivation:completion:] + 236
    frame #15: 0x000000011010c3db UIKit`-[__UICanvasLifecycleMonitor_Compatability activateEventsOnly:withContext:completion:] + 675
    frame #16: 0x0000000110a7d614 UIKit`__82-[_UIApplicationCanvas _transitionLifecycleStateWithTransitionContext:completion:]_block_invoke + 299
    frame #17: 0x0000000110a7d4ae UIKit`-[_UIApplicationCanvas _transitionLifecycleStateWithTransitionContext:completion:] + 433
    frame #18: 0x000000011076175d UIKit`__125-[_UICanvasLifecycleSettingsDiffAction performActionsForCanvas:withUpdatedScene:settingsDiff:fromSettings:transitionContext:]_block_invoke + 221
    frame #19: 0x000000011095c4b7 UIKit`_performActionsWithDelayForTransitionContext + 100
    frame #20: 0x0000000110761627 UIKit`-[_UICanvasLifecycleSettingsDiffAction performActionsForCanvas:withUpdatedScene:settingsDiff:fromSettings:transitionContext:] + 223
    frame #21: 0x00000001104de0e0 UIKit`-[_UICanvas scene:didUpdateWithDiff:transitionContext:completion:] + 392
    frame #22: 0x000000010fd47eac UIKit`-[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 515
    frame #23: 0x000000011031abcb UIKit`-[UIApplicationSceneClientAgent scene:didInitializeWithEvent:completion:] + 361
    frame #24: 0x00000001150d72f3 FrontBoardServices`-[FBSSceneImpl _didCreateWithTransitionContext:completion:] + 331
    frame #25: 0x00000001150dfcfa FrontBoardServices`__56-[FBSWorkspace client:handleCreateScene:withCompletion:]_block_invoke_2 + 225
    frame #26: 0x000000011420c7ec libdispatch.dylib`_dispatch_client_callout + 8
    frame #27: 0x0000000114211db8 libdispatch.dylib`_dispatch_block_invoke_direct + 592
    frame #28: 0x000000011510b470 FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
    frame #29: 0x000000011510b12e FrontBoardServices`-[FBSSerialQueue _performNext] + 439
    frame #30: 0x000000011510b68e FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 45
    frame #31: 0x000000011304abb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #32: 0x000000011302f4af CoreFoundation`__CFRunLoopDoSources0 + 271
    frame #33: 0x000000011302ea6f CoreFoundation`__CFRunLoopRun + 1263
    frame #34: 0x000000011302e30b CoreFoundation`CFRunLoopRunSpecific + 635
    frame #35: 0x00000001159a0a73 GraphicsServices`GSEventRunModal + 62
    frame #36: 0x000000010fd4b057 UIKit`UIApplicationMain + 159
    frame #37: 0x000000010eba5a67 ***`main at AppDelegate.swift:12
    frame #38: 0x0000000114289955 libdyld.dylib`start + 1
    frame #39: 0x0000000114289955 libdyld.dylib`start + 1
(lldb)


Then it is set by _UIColorAttributeTraitStorage applyRecordsMatchingTraitCollection after that.


(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x000000010eba36b2 ***`MyView.backgroundColor.didset(oldValue=0x0000608000272000, self=0x00007fb97e707eb0) at MyView.swift:17
    frame #1: 0x000000010eba3674 ***`MyView.backgroundColor.setter(newValue=0x0000608000036240, self=0x00007fb97e707eb0) at MyView.swift:0
    frame #2: 0x000000010eba35ac ***`@objc MyView.backgroundColor.setter at MyView.swift:0
    frame #3: 0x000000010eed0b47 Foundation`-[NSObject(NSKeyValueCoding) setValue:forKey:] + 292
    frame #4: 0x000000010fe19f77 UIKit`-[UIView(CALayerDelegate) setValue:forKey:] + 172
    frame #5: 0x00000001100dcac3 UIKit`-[NSObject(UIIBPrivate) _uikit_applyValueFromTraitStorage:forKeyPath:] + 50
    frame #6: 0x00000001100de12d UIKit`-[_UIColorAttributeTraitStorage applyRecordsMatchingTraitCollection:] + 160
    frame #7: 0x00000001100dd38f UIKit`-[NSObject(_UITraitStorageAccessors) _applyTraitStorageRecordsForTraitCollection:] + 261
    frame #8: 0x000000010fdeecf0 UIKit`-[UIView _traitCollectionDidChangeInternal:] + 77
    frame #9: 0x000000010fdeef1d UIKit`-[UIView _wrappedProcessTraitCollectionDidChange:forceNotification:] + 163
    frame #10: 0x000000010fdef21f UIKit`-[UIView _processDidChangeRecursivelyFromOldTraits:toCurrentTraits:forceNotification:] + 129
    frame #11: 0x000000010fe1a740 UIKit`-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1411
    frame #12: 0x0000000116817456 QuartzCore`-[CALayer layoutSublayers] + 177
    frame #13: 0x000000011681b667 QuartzCore`CA::Layer::layout_if_needed(CA::Transaction*) + 395
    frame #14: 0x00000001167a20fb QuartzCore`CA::Context::commit_transaction(CA::Transaction*) + 343
    frame #15: 0x00000001167cf79c QuartzCore`CA::Transaction::commit() + 568
    frame #16: 0x000000010fd65269 UIKit`__34-[UIApplication _firstCommitBlock]_block_invoke_2 + 141
    frame #17: 0x000000011304ab0c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
    frame #18: 0x000000011302f2db CoreFoundation`__CFRunLoopDoBlocks + 331
    frame #19: 0x000000011302ea84 CoreFoundation`__CFRunLoopRun + 1284
    frame #20: 0x000000011302e30b CoreFoundation`CFRunLoopRunSpecific + 635
    frame #21: 0x00000001159a0a73 GraphicsServices`GSEventRunModal + 62
    frame #22: 0x000000010fd4b057 UIKit`UIApplicationMain + 159
    frame #23: 0x000000010eba5a67 ***`main at AppDelegate.swift:12
    frame #24: 0x0000000114289955 libdyld.dylib`start + 1
    frame #25: 0x0000000114289955 libdyld.dylib`start + 1
(lldb)

Replies

It doesn't seem impossible that colors might be adjusted when the view is added to a window view hierarchy, without being documented anywhere.


I'd suggest you try setting your overriding colors in viewWillAppear or (if that's still too early) in viewDidAppear.


Might be worth a bug report, too.

Since colors can be based on values that may not exist until the view is loaded - it makes sense that NIB named colors may not be available until then.

viewDidLoad refers to your viewController’s code. After the viewController is loaded the nib file is loaded along with all your instructions in the interface builder and all those colors and frame positioning. Then viewWillAppear is executed. So you can override the nib file there. Note that if you don’t want to repeatedly execute that viewWillAppear code each time the view will appear you can set a variable like firstTime to YES in viewDidLoad and set it to NO at the end of viewWillAppear.

Got bitten by that too. Hard. Here's a detailed investigation into what happens under the hood. In a nutshell, UIKit applies the trait collection at the very last moment, effectively resetting any changes you make to the original state of the nib. Given that the bug appeared in iOS 11.0 and is still present in iOS 12.0, chances that it will be ever fixed are pretty low.

Just wanted to say "thanks a lot!" everyone, especially Costique, this has been driving me absolutely nuts all night. I had a view attached to an UITableViewCell and I was setting its background to a certain color immediately after it was dequeued from the table view, but it would always revert to what was set in the xib. I tried to isolate the problem in a separate project, but it was working correctly and the backgroundColor setter was called only twice, while my original project setup caused it to be called 4 or 5 times.

After studying the stack trace for a while I noticed to traits reference which lead me to this thread, I never would have thought that using named colors would lead to this behavior.