iosstoryboarduiappearance

UIAppearance tintColor takes priority over Storyboard tintColor


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.

Interface Builder

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:

Simulator

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).


Solution

  • 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.