Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIColor return wrong values for dark mode colors

I have a custom UITextField subclass which changes its border color when typing something in it. I'm listening for changes by calling

self.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) 

and then, in textFieldDidChange(_:) I'm doing:

self.layer.borderColor = UIColor(named: "testColor")?.cgColor 

Where testColor is a color defined in Assets.xcassets with variants for light and dark mode. The issue is that UIColor(named: "testColor")?.cgColor seems to always return the color for the light mode.

Is this a bug in iOS 13 beta or I'm doing something wrong? There's a GitHub repo with the code which exhibits this behaviour. Run the project, switch to dark mode from XCode then start typing something in the text field.

like image 864
ov1d1u Avatar asked Jul 23 '19 13:07

ov1d1u


People also ask

How do I create a custom color in UIKit?

Color objects can be created in a variety of ways: UIKit provides standard system colors for matching the colors in your own UI objects to those provided by UIKit. UIKit provides definitions for standard colors such as red, orange, yellow, and so on. You can create a custom color in a Core Graphics color space.

How does dark mode work on iOS 12?

One of the changes that makes adopting Dark Mode so easy is the new system colors API from UIColor. On iOS 12 and older, you might have a label you want to make black, and it would work just fine - in fact, black was the default color for UILabel s. But in Dark Mode, the background will also be black, which means the text won't be visible.

What is the difference between light and dark mode in Revit?

Notice the change in foreground content (table view cell) color in dark mode. In light color style, both foreground contents are white, but in dark mode, white don't always translate to black.

What are the new colors in iOS 13?

iOS 13 has 23 new element colors ( label etc) and one new standard color ( systemIndigo ). However, even system colors that have been around for a while (like systemRed) have become dynamic in iOS 13 - they might actually be different colors in dark mode vs light mode. (Skip to the bottom if you'd like to see a list of the new colors.)


1 Answers

Short answer

In this situation, you need to specify which trait collection to use to resolve the dynamic color.

self.traitCollection.performAsCurrent {     self.layer.borderColor = UIColor(named: "testColor")?.cgColor } 

or

self.layer.borderColor = UIColor(named: "testColor")?.resolvedColor(with: self.traitCollection).cgColor 

Longer answer

When you call the cgColor method on a dynamic UIColor, it needs to resolve the dynamic color's value. That is done by referring to the current trait collection, UITraitCollection.current.

The current trait collection is set by UIKit when it calls your overrides of certain methods, notably:

  • UIView
    • draw()
    • layoutSubviews()
    • traitCollectionDidChange()
    • tintColorDidChange()
  • UIViewController
    • viewWillLayoutSubviews()
    • viewDidLayoutSubviews()
    • traitCollectionDidChange()
  • UIPresentationController
    • containerViewWillLayoutSubviews()
    • containerViewDidLayoutSubviews()
    • traitCollectionDidChange()

However, outside of overrides of those methods, the current trait collection is not necessarily set to any particular value. So, if your code is not in an override of one of those methods, and you want to resolve a dynamic color, it's your responsibility to tell us what trait collection to use.

(That's because it's possible to override the userInterfaceStyle trait of any view or view controller, so even though the device may be set to light mode, you might have a view that's in dark mode.)

You can do that by directly resolving the dynamic color, using the UIColor method resolvedColor(with:). Or use the UITraitCollection method performAsCurrent, and put your code that resolves the color inside the closure. The short answer above shows both ways.

You could also move your code into one of those methods. In this case, I think you could put it in layoutSubviews(). If you do that, it will automatically get called when the light/dark style changes, so you wouldn't need to do anything else.

Reference

WWDC 2019, Implementing Dark Mode in iOS

Starting at 19:00 I talked about how dynamic colors get resolved, and at 23:30 I presented an example of how to set a CALayer's border color to a dynamic color, just like you're doing.

like image 162
Kurt Revis Avatar answered Sep 28 '22 09:09

Kurt Revis