I created a simple view controller using Interface Builder that contains an UIImageView and UIButton. On both of them, I set tintColor
to be Magenta.
On the AppDelegate, I set UIView.appearance.tintColor
to be Cyan.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIView.appearance.tintColor = UIColor.cyanColor;
return YES;
}
When running the app I get this:
This doesn't make sense to me. I expect the Magenta tintColor specified directly on the imageView and button to override the Cyan tintColor.
Why is this happening?
For reference, I'm using Xcode Version 11.2 (11B52).
It turns the tintColor
property is not decorated with UI_APPEARANCE_SELECTOR
, so it shouldn't be used in UIAppearance proxies.
Even if it seems to work, setting UIView.appearance.tintColor
has side effects. One instance is this issue with storyboards. Another case I've noticed is that, in iOS 13, UITableView section headers would become filled with the set color.
Given it's not a supported scenario, it's hard to tell exactly why attempting to override tintColor
for a component on a storyboard doesn't work. Checking again the documentation, I found a note which might explain it though:
iOS applies appearance changes when a view enters a window, it doesn’t change the appearance of a view that’s already in a window. To change the appearance of a view that’s currently in a window, remove the view from the view hierarchy and then put it back.
I assume that the "storyboard" tintColor will be set while loading the storyboard object, and then UIKit will overwrite it with the "UIAppearance" tintColor once the view is inserted in the view hierarchy.
The solution
The correct way to set a global tintColor seems to set it on the main UIWindow
. According to the comments in UIView.h, tintColor is inherited by the superview. This allows to use a global tintColor for all views and override it locally where needed. It works as expected when setting the property from a storyboard.