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.
Related
I tried the steps below to expeand UIColor, but it didn't work.
At first, I added new color set in xcassets,
and then add UIColor + Extension.swift in my project folder.
// UIColor + Extension.swift
import Foundation
import UIKit
extension UIColor {
class var testColor1:UIColor {
return UIColor(red: 210.0/255.0, green: 105.0/255.0, blue: 130.0/255.0, alpha: 1.0)
}
class var testColor2: UIColor? { return UIColor(named: "testColor") }
}
I want to load custom color in AppDelegate.mm, but got the following error.
animationUIView.backgroundColor = [UIColor testColor1];
The code above doesn't work either.
What am I doing wrong?
I'm sorry, but I'm making a project with react native, so I don't know Swift and objective c well.
if you want to change a background with a custom color you should use :
someView.backgroundColor = UIColor(named: "testColor1")
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.
I'm creating a simple Custom NSView that allows the user to input 3 text fields each for R,G and B values respectively. I already referenced this question and answer set here - it only accounts up to Swift 2.0. So, does anyone know the correct technique of obtaining this same effect in Swift 3.0? I can't seem to get anything that I'm doing to work. I keep getting errors of all sorts.
Here's the current technique I'm using (This seems to work... BUT it won't account for the other 2 RGB values, since it's used in the #IBAction):
//viewDidLoad
RVCustomView.wantsLayer = true
#IBAction func redValue(_ sender: AnyObject) {
print(red.stringValue)
var rValue = 0
let str = red.stringValue
if let n = NumberFormatter().number(from: str) {
rValue = Int(CGFloat(n))
}
CustomNSView.layer?.backgroundColor = CGColor(red: rValue, green: 255, blue: 255, alpha: 255)
}
If you want to dynamically change the background colour of a NSView each time the value of one of the three R, G, B text views changes, you could use the controlTextDidChange(notification:) delegate method of NSTextField coupled with outlets for each of the three text fields.
The method is fired every time one of the fields is changed and you use it for reading the value of the RGB fields (via outlets) and change the colour accordingly.
override func controlTextDidChange (notification: NSNotification?) {
// assuming the text fields' value is between 0 and 255
guard
let redValue = Float(redOutlet.stringValue),
let greenValue = Float(greenOutlet.stringValue),
let blueValue = Float(blueOutlet.stringValue)
else { return }
CustomNSView.layer?.backgroundColor = CGColor(red: CGFloat(redValue/255), green: CGFloat(greenValue/255), blue: CGFloat(blueValue/255), alpha: 255)
}
Note : don't forget to properly set the delegate for each of the three text fields!
I think you can just do that:
#IBAction func redValue(_ sender: AnyObject) {
yourView.backgroundColor = UIColor(colorLiteralRed: readValue/255, green: greenValue/255, blue: blueValue/255, alpha: 1)
}
So, while working in swift, i made a view in a viewController where when I tap another view in the same ViewController, it generates an array with value
[arc4random_uniform(257), arc4random_uniform(257), arc4random_uniform(257)]
where the random numbers are parameters for random RBG colors, and I've only sort-of made it work. I want it so that every time the user (or me ;_;) taps on the view with the Tap Gesture Recognizer, it generates another random color. I don't really know how to "re-roll" the Array so that it generates another random color.
If you want to get just a random UIColor, you can create a function which gets called everytime you touch the recognizer. Also, as mattt mentioned in the comments, you should change the max value from 257 to 256.
func randomColorCreator()->UIColor{
var red:CGFloat = CGFloat(arc4random_uniform(256))/CGFloat(255)
var green:CGFloat = CGFloat(arc4random_uniform(256))/CGFloat(255)
var blue:CGFloat = CGFloat(arc4random_uniform(256))/CGFloat(255)
return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
}
If you really want to get the three color-values, you can return the three values like that:
func randomColorCreatorV2()->(red:Int, green:Int, blue:Int){
var red: = arc4random_uniform(256)
var green: = arc4random_uniform(256)
var blue: = arc4random_uniform(256)
return (red, green, blue)
}
You than access the values like that in your TapGestureRecognizer method:
var randomColors = randomColorCreatorV2()
var blueValue = randomColors.blue
I've found a serious swift-bug in SpriteKit while working with SKSpriteNodes and their colors.
This code works fine on all iPhones beside the iPhone 5S:
var color1 = UIColor(red: 123/255, green: 123/255, blue: 123/255, alpha: 1)
var color2 = UIColor(red: 123/255, green: 123/255, blue: 123/255, alpha: 1)
var sprite = SKSpriteNode(color: color1, size: CGSizeMake(100, 100))
if(sprite.color == color2){
println("Same color")
}
As you see, the two colors are the absolut same. But on the iPhone 5S simulator, the if isn't called.
Has somebody else the same problem and can provide a solution?
According to the documentation here:
Sprite Kit works only with solid colors. For best results, use the
preset colors provided by the platform class or a custom color defined
in the RGBA device color space.
As a result somehow the SKSpriteNode has made some changes to the color parameter in the init function. You can see it if you call encode:
sprite.color.encode() // 140,646,370,382,768
color1.encode() // 140,646,367,110,928
If you use predefined color values, then your problem goes away:
var color3 = UIColor.blueColor()
var sprite3 = SKSpriteNode(color: color3, size: CGSizeMake(100, 100))
sprite3.color == color3 // true
You are comparing pointer values, not the actual color. Seeing that these are UIColor instances, you have to compare them using isEqual (showing ObjC code as I don't know what it looks like in Swift - or perhaps Swift is in fact using isEqual behind the scenes):
if ([sprite.color isEqual:color2])
If implemented correctly by UIColor this will compare the actual color values rather than the pointers.