Why does NSColor.controlTextColor change according to background color? - swift

I'm working on a Cocoa application and I find that as long as the font color of NSTextField is set to NSColor.controlTextColor, the font will change according to the background color of NSTextField.
For example, when I set the background color to white, the font becomes black.
But when I set the background color to black, the font turns white.
I want to define an NSColor to achieve the same effect. How to achieve it?

If you want to pass in any color and then determine which text color would be more ideal - black or white - you first need to determine the luminance of that color (in sRGB). We can do that by converting to grayscale, and then checking the contrast with black vs white.
Check out this neat extension that does so:
extension NSColor {
/// Determine the sRGB luminance value by converting to grayscale. Returns a floating point value between 0 (black) and 1 (white).
func luminance() -> CGFloat {
var colors: [CGFloat] = [redComponent, greenComponent, blueComponent].map({ value in
if value <= 0.03928 {
return value / 12.92
} else {
return pow((value + 0.055) / 1.055, 2.4)
}
})
let red = colors[0] * 0.2126
let green = colors[1] * 0.7152
let blue = colors[2] * 0.0722
return red + green + blue
}
func contrast(with color: NSColor) -> CGFloat {
return (self.luminance() + 0.05) / (color.luminance() + 0.05)
}
}
Now we can determine whether we should use black or white as our text by checking the contrast between our background color with black and comparing it to the contrast with white.
// Background color for whatever UI component you want.
let backgroundColor = NSColor(red: 0.5, green: 0.8, blue: 0.2, alpha: 1.0)
// Contrast of that color w/ black.
let blackContrast = backgroundColor.contrast(with: NSColor.black.usingColorSpace(NSColorSpace.sRGB)!)
// Contrast of that color with white.
let whiteContrast = backgroundColor.contrast(with: NSColor.white.usingColorSpace(NSColorSpace.sRGB)!)
// Ideal color of the text, based on which has the greater contrast.
let textColor: NSColor = blackContrast > whiteContrast ? .black : .white
In this case above, the backgroundColor produces a contrast of 10.595052467245562 with black and 0.5045263079640744 with white. So clearly, we should use black as our font color!
The value for black can be corroborated here.
EDIT: The logic for the .controlTextColor is going to be beneath the surface of the API that Apple provides and beyond me. It has to do with the user's preferences, etc. and may operate on views during runtime (i.e. by setting .controlTextColor, you might be flagging a view to check for which textColor is more legible during runtime and applying it).
TL;DR: I don't think you have the ability to achieve the same effect as .controlTextColor with an NSColor subclass.
Here's an example of a subclassed element that uses its backgroundColor to determine the textColor, however, to achieve that same effect. Depending on what backgroundColor you apply to the class, the textColor will be determined by it.
class ContrastTextField: NSTextField {
override var textColor: NSColor? {
set {}
get {
if let background = self.layer?.backgroundColor {
let color = NSColor(cgColor: background)!.usingColorSpace(NSColorSpace.sRGB)!
let blackContrast = color.contrast(with: NSColor.black.usingColorSpace(NSColorSpace.sRGB)!)
let whiteContrast = color.contrast(with: NSColor.white.usingColorSpace(NSColorSpace.sRGB)!)
return blackContrast > whiteContrast ? .black : .white
}
return NSColor.black
}
}
}
Then you can implement with:
let textField = ContrastTextField()
textField.wantsLayer = true
textField.layer?.backgroundColor = NSColor.red.cgColor
textField.stringValue = "test"
Will set your textColor depending on the layer's background.

Related

Set Color Dark & Light Mode for label background

class func setColorLightDarkMode() -> UIColor {
let aColor = UIColor(named: "StatusBackGroundColor") ?? .gray
return aColor
}
label.backgroundColor = setColorLightDarkMode()
Added Color Assert for BackGroundColor and set AnyAppearance & DarkAppearance with two different color.
set default set to .gray, how to avoid default value .gray
for setting dark & light mode for background Color
Which is best way to to have making setColorLightDarkMode return type optional ? or UIColor and set ?? .defaultColor

iOS 13 system color for UIButton

Apple recommends using system colors to adapt apps to light and dark mode automatically, for example:
myLabel.textColor = UIColor.secondaryLabel
Here Apple lists various properties to be used, such as the one in the example above, and system colors for background, placeholder text, and more.
But it doesn't list a property for UIButton elements.
Which property or other method should we use to adapt UIButtons to theme changes?
As of now, I'm doing this:
myButton.tintColor = UIColor.link
which is supposedly for links but is the only "clickable" property I found.
I'm not looking to use something like UIColor.systemRed, rather something like UIColor.systemBackground, which adapts automatically to the current theme.
I hope you create colored Assets not one by one. You can use this function to tint images as a extension of UIImageView. I also use the same technique for buttons.
func setImageAndColor(image: UIImage, color: UIColor) {
let templateImage = image.withRenderingMode(.alwaysTemplate)
self.image = templateImage
self.tintColor = color
}
In case you want to define all you own colors, I suggest to create a singleton class named Colors:
import UIKit
class Colors {
static let shared = Colors()
var statusBarStyle: UIStatusBarStyle = .lightContent
private init(){}
func setLightColors() {
statusBarStyle = .darkContent
yourColor = UIColor( // choose your favorite color
styleColor = UIColor(red: 255/255, green: 255/255, blue: 255/255, alpha: 1)//white
labelColor = UIColor(red: 15/255, green: 15/255, blue: 15/255, alpha: 1)
subLabelColor = UIColor(red: 25/255, green: 25/255, blue: 25/255, alpha: 1)
............ set values for all colors from here.
}
func setDarkColors() {
statusBarStyle = .lightContent
yourColor = // choose your favorite color
............
}
// set initial colors
var yourColor: UIColor =
}
If somebody is interested in the whole Colors class, text me or comment below.
I access the colors singleton by:
Colors.shared.yourColor
Also for first configuration I set in the very first VC the darkmode number (0-Auto; 1-On; 2-Off):
if darkmodeNumber == 0 {
if traitCollection.userInterfaceStyle == .light {
print("Light mode")
Colors.shared.setLightColors()
} else {
print("Dark mode")
Colors.shared.setDarkColors()
}
} else if darkmodeNumber == 1 {
Colors.shared.setDarkColors()
} else if modeNumber == 2 {
Colors.shared.setLightColors()
}
}
The statusbar should then change also the right way.
Use any system colors you like. They are all adaptive. I applied the system gray color to a button's text:
The color changes when we switch between light and dark mode.

Obtaining RGB color from UIButton (UIColor) backgroundColor in Swift

I have three UIButtons and each has a different (UIColor) backgroundColor; for example, the backgroundColor of one UIButton is blue, one is pink, the other is orange. When I click each UIButton, I want to obtain the exact RGB (Red, Green, Blue) color values directly from it's backgroundColor property. (For example, if I clicked the UIButton with the pink backgroundColor, I will get a returned RGB values- R: 255, G: 0, B: 128.) Another way to explain this is, I want to convert a UIButton's UIColor backgroundColor into UIColor RGB values.
In Swift, what is the most simple, most efficient code to extract the RGB (Red, Green, Blue) color values of a UIButton's backgroundColor and then display the result in a UILabel?
Your task consists of three parts:
Obtaining the button that has been clicked from the click event handler
Given a UIButton obtain its background color, and
Given a UIColor obtain its RGB components
The first task is simple: add sender to the method that processes the click. The second task is also straightforward - all you need to do is accessing backgroundColor property. Finally, to get the components you need to call getRGB.
#IBAction func mainButton(button: UIButton) {
let bgColor:UIColor = button.backgroundColor!
var r : CGFloat = 0
var g : CGFloat = 0
var b : CGFloat = 0
var a: CGFloat = 0
if bgColor.getRed(&r, green: &g, blue: &b, alpha: &a) {
... r, g, b, and a represent the component values.
}
}
Note that it would be much simpler to do this the MVC way, i.e. by retrieving the components pre-stored in the model. Set your button tags to 0, 1, and 2, make a look-up table, and use it to perform the task:
let componentForTag: Int[][] = [[255, 0, 128], [128, 0, 0],[128, 128, 0]]
...
#IBAction func mainButton(button: UIButton) {
let components = componentForTag[button.tag]
// That's it! components array has the three components
}

Continuously changing color property of SKShapeNode during gameplay (Swift)

I have an SKShapeNode var Circle = SKShapeNode(circleOfRadius: radius) in the background of my spritekit game that uses swift. The circle is for aesthetic purposes so nothing interacts with it. I'd like Circle.strokeColor to continuously change at all times. My current code changes the stroke color property of the SKShapeNode but it does not display the color changes because I'm changing the property after it has been added to the background. The color stays the same throughout the game until the the game ends and the circle is removed from the background then recreated. My code changes the color by adding 1 to var colorTime: CGFloat = 0.0 every time the update function runs, and then relating the RGB values of the Circle's color to cosine functions using that colorTime variable. How can I continuously change (and display) the color of the circle?
override func update(currentTime: CFTimeInterval) {
if last_update_time == 0.0 {
delta = 0
} else {
delta = currentTime - last_update_time
}
last_update_time = currentTime
colorTime += 100
redColor = (cos(colorTime/100)+1)/2
greenColor = (cos(colorTime/200 - 2.09)+1)/2
blueColor = (cos(colorTime/300 - 4.18)+1)/2
circleColor = UIColor(red: redColor, green: greenColor, blue: blueColor, alpha: 1)
Circle.strokeColor = circleColor
}
I didn't realize this code actually works. The only issue ended up being that the game would crash after the game restarts because I would add the SKShapeNode to the background again without ever removing it. I added Circle.removeFromParent() to my restartGame function and now I'm good.

How to get color components of a CGColor correctly

I have a UILabel with black color;
i am writing the following code to get black color's components.
UIColor *aColor = [aLabel.textColor retain];
const CGFloat* components = CGColorGetComponents(aColor.CGColor);
CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();
but always this is giving Green color components instead of black color components;
Is any one have idea about this?
Am i passing different color space?
:-( Please help me.
Most likely wrong colorspace. I guess the alpha component of the grayscale colorspace gets where you think the green component should be.
I use this function to create a string from UIColor, I only encounter RGB and Grayscale colorspaces, so I just interpret every color with less than 4 (R+G+B+A) components as grayscale.
if (CGColorGetNumberOfComponents(color.CGColor) < 4) {
const CGFloat *components = CGColorGetComponents(color.CGColor);
color = [UIColor colorWithRed:components[0] green:components[0] blue:components[0] alpha:components[1]];
}
if (CGColorSpaceGetModel(CGColorGetColorSpace(color.CGColor)) != kCGColorSpaceModelRGB) {
NSLog(#"no rgb colorspace");
// do seomthing
}
const CGFloat *components = CGColorGetComponents(color.CGColor);
NSString *colorAsString = [NSString stringWithFormat:#"%f,%f,%f,%f", components[0], components[1], components[2], components[3]];
of course this method is not save for all cases, you should adopt it to your requirements.
The reason you are seeing what looks like r=0, g=1, b=0, a=0 is because you are misinterpreting the values in the returned array as being in an RGB color model. UIColor uses monochrome colorspace for greyscale colors like black in this case.
What you are seeing is an array of 2 components from a monochrome color model. The first is gray level (0 for black) and the second is alpha (1 for opaque). The last two values your are looking at are off the end of the 2 element array and happen to be 0 in this case.
You'll notice if color is black and you try CGColorGetNumberOfComponents(color.CGColor), it returns 2. And if you try CGColorSpaceGetModel(CGColorGetColorSpace(color.CGColor)), it returns 0 which corresponds to kCGColorSpaceModelMonochrome (see the enum CGColorSpaceModel in CGColorSpace.h)
see CGColorSpace Reference
I think this is a very nice way to get the rgb representation of any UIColor*, which already has a convenience method for retaining it´s components.
-(CGColorRef)CGColorRefFromUIColor:(UIColor*)newColor {
CGFloat components[4] = {0.0,0.0,0.0,0.0};
[newColor getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]];
CGColorRef newRGB = CGColorCreate(CGColorSpaceCreateDeviceRGB(), components);
return newRGB;
}
One way to get RGB for these is by drawing the color into a graphics context and reading the color back out. See my answer to this question for example code:
Get RGB value from UIColor presets
working exampel with UIColor:
CALayer * btnLayer = [myLittleButton layer];
[layer setBackgroundColor:<#(CGColorRef)#>] becomes: [btnLayer setBorderColor:[[UIColor grayColor] CGColor]];
just make sure the buttons original colors doesnt cover the layer
extension UIColor {
func toRGB() -> UIColor? {
guard let model = cgColor.colorSpace?.model else {
return nil
}
let components = cgColor.components
switch model {
case .rgb:
return UIColor(red: components?[0] ?? 0.0, green: components?[1] ?? 0.0, blue: components?[2] ?? 0.0, alpha: components?[3] ?? 0.0)
case .monochrome:
return UIColor(red: components?[0] ?? 0.0, green: components?[0] ?? 0.0, blue: components?[0] ?? 0.0, alpha: components?[1] ?? 0.0)
default:
return nil
}
}
}