iOS13: How to specify color for elevated user interface level in the asset catalog - uicolor

iOS 13 brings us UIUserInterfaceLevel, which can be either .base or .elevated. The system seems to automatically adjust colors provided to UIView when the elevated level is used in dark mode.
However, there seems to be no way how to manually specify the .elevated color in the asset catalog, or is it?
The only way how to do it seems to be via the new UIColor constructor:
UIColor.init { (traits) -> UIColor in
traits.userInterfaceLevel == .elevated ? UIColor(named: "myColor-elevated")! : UIColor(named: "myColor")!
}

There is no way to do that with color assets, as far as I know.
When you use the system background and fill colors, iOS will automatically pick the "next higher" color when on an elevated level, i.e., .systemBackground becomes .secondarySystemBackground, etc.

My solution was to combine .xcassets with this trait-dependent initializer. For each color I created a color set with light and dark color and another color set that only contains the elevated color (for any variant).
Then I added an extension to UIColor. This extension provides easy access to all the colors throughout the app and contains the logic for selecting the right color.
extension UIColor {
static let backgroundPrimary = UIColor { (trait) -> UIColor in
switch (trait.userInterfaceStyle, trait.userInterfaceLevel) {
case (.dark, .elevated):
// For this color set you can set "Appearances" to "none"
return UIColor(named: "backgroundPrimaryElevated") ?? .black
default:
// This color set has light and dark colors specified
return UIColor(named: "backgroundPrimary") ?? .black
}
}
// ... All the other colors
}

Related

Accessing system colors in WatchKit

I am trying to use UIKit colors for my Apple Watch interface. You can do it in iOS with UIColor class properties like .systemBackground and others. For example:
let myColor = UIColor.systemBlue
Gives me error when compiling watchOS extension target:
Type 'UIColor' has no member 'UIColor.systemBlue'.
I wonder tis there alternative way to get this system colors, and if not — what I need is to get a color of some UI elements, like SwiftUI table view row background which looks very similar to UIColor.systemGray5.
#State private var isActive = false
var body: some View {
List {
Section() {
ForEach(self.dataSource) { item in
CustomCell()
.listRowPlatterColor(self.isActive ? Color(UIColor.systemGray5) : Color(.orange))
}
}
}
}
Code above won't compile because UIColor.systemGray5 is not found in WatchKit. I have tried to create a class which is sued both in iPhone and Watch target and it fixes Xcode autocompletion. But the app breaks when trying to use it the Apple Watch target.
I hope there's a way rather then finding correct color and hard-coding it.
Looking at the documentation, the UIColor.system-ish are only available for iOS, Catalyst and some for tvOS. What you can do is extend UIColor and create you systemGray5:
extension UIColor {
static var mySystemGray5: UIColor {
return .init(red: 229/255, green: 229/255, blue: 234/255, alpha: 1)
}
}
And use on your code like:
ForEach(self.dataSource) { item in
CustomCell()
.listRowPlatterColor(self.isActive ? Color(UIColor.mySystemGray5) : Color(.orange))
}
Since you are using SwiftUI, Color.blue is equivalent to UIColor.systemBlue.
Unfortunately, Color doesn't have a lots of UIColor system colors, hopefully Apple add missing colors on WWDC 2020.
May be use can use something like Color.gray.opacity.opacity(0.25) in the meanwhile?

Customize background and text color in UIDatePicker in iOS 12

In previous iOS versions, I was able to change the text and background colors using the something like the following:
let datePicker = UIDatePicker()
datePicker.datePickerMode = UIDatePickerMode.date
datePicker.setValue(UIColor.white, forKey: "textColor")
datePicker.backgroundColor = UIColor.black.withAlphaComponent(0.6)
This is no longer working in Xcode 10/iOS 12. The date picker is displayed with its default colors. Has anyone found a solution for customizing the text and background colors in iOS 12?
It seems something is resetting those values between when I set them and when the view is displayed. I was able to solve this by subclassing UIDatePicker and setting the values in layoutSubviews(). There may be a more performant place to set the colors, but this is working for me for now.
class DatePicker: UIDatePicker {
override func layoutSubviews() {
super.layoutSubviews()
backgroundColor = UIColor.black.withAlphaComponent(0.6)
setValue(false, forKey: "highlightsToday")
setValue(UIColor.white, forKey: "textColor")
}
}

Set UIBarButtonItem gray color when disabled

I have an extension for UIColor to get color from hex string. I'm using it as per below:
self.navigationItem.rightBarButtonItem?.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor(hexString: "#C0BFC0")], for: UIControlState.disabled)
self.navigationItem.rightBarButtonItem?.isEnabled = false
For some strange reason the color of the rightBarButtonItem is the same as before. Is there a way to change it when disabled? I have the above in my viewDidLoad function
I tried reading the below:
UIBarButtonItem is disabled, but has normal color
Change color of disabled bar button item in iOS
I'm able to change the color when it is not disabled. Seems when its disabled the colors are not obeyed?
when its disabled the colors are not obeyed?
I hit this bug with some toolbar items. My workaround is to ensure that the UIBarButtonItem title changes at runtime, when the disabled color should change. To do this, change the disabled color, then force the title to change by adding an invisble Unicode space if needed.
For example in Swift:
let zeroWidthSpaceStr = "\u{200B}"
func forceChangeItemTitle(_ item:UIBarButtonItem, newTitle:String) {
// Ensure the button item title is changed. Needed to pick up change in disabled text color
var newTitle = newTitle
if item.title == newTitle {
// Title already set, so change it invisibly
newTitle += zeroWidthSpaceStr
}
item.title = newTitle
}

How to change the action of NSColorWell?

I can use NSColorWell as button to change the color of selected text. Since NSColorWell is object of an NSControl it has target and action. I guess, the action is implementing the code to change the color of the selected text in NSTextView. Where can I find this code for NSColorWell action? I would like to change it in away that I can use NSColorWell to change the background of the selected text, and ultimately to have in ToolBar two NSColorWell buttons: one to change text's foreground color and second one for text's background color.
NSColorWell is just a rectangular control to change a color.
You can either create an IBAction and connect it to the action of the color well in the Connections Inspector (⌥⌘6) of Interface Builder
#IBAction func changeColor(_ sender : NSColorWell)
{
let color = sender.color
// do something with the color
}
Or bind the value in Bindings Inspector (⌥⌘7) of Interface Builder to a dynamic property, this example will set the color well to a default value of green.
dynamic var color : NSColor = .green {
didSet {
// do something with the color
}
}

How to invert text colour of selected NSTableView row

I have an NSTableView in which I'm changing the colour of the text in a particular column, but when a row is selected, the text does not change to a more appropriate colour so that it's readable.
Finder's Date Modified, Size, and Kind columns have grey text, and when you select a file/folder row, the grey text changes to white (so that it's readable within the blue highlight).
I can't find a magic checkbox on XCode to enable this behaviour by default, so does anybody know how I might achieve the same effect?
I'm using Swift in XCode 6.3.
Thanks.
You don't say what view or view hierarchy you're using for your cells. You also don't say how or where you're setting the text fields' color or to what color, specifically.
When a row is selected, the row automatically computes its interiorBackgroundStyle. It also sets the backgroundStyle of the cell view if it responds to -setBackgroundStyle: or is an NSControl with a cell which responds to that.
If your cell view is an instance of NSTableCellView, it forwards the background style to all of its subviews which meet the same criteria. If you use a different container view as your cell view and you want the background style forwarded along like this, you would have to implement that yourself in your view class.
An NSTextField's cell (an NSTextFieldCell) responds to -setBackgroundStyle: and so has its background style set automatically by the above mechanisms. The text field cell will automatically change its text color to white if its textColor is one of the standard control colors (e.g. NSColor.controlTextColor()), but won't do so if you assign a non-standard color. So, if you're setting a specific color for your text, you are responsible for changing that when the background style changes.
You can use a subclass of NSTableCellView and add a property observer (didSet) for the backgroundStyle property. That can change the text field's textColor depending on the style that was set. For example, you can use your custom color if the background style is not .Dark or use the normal text field color NSColor.controlTextColor() if it is .Dark (so that the text field will actually display it as white).
You could also use a subclass of NSTextFieldCell for your text field and do the same sort of thing. Or override drawInteriorWithFrame(_:inView:) to draw with a different text color depending on the background style.
With the help of Ken's response (above) I was able to get it to work. Here's a rough draft that does what I want it to:
import Cocoa
class CustomTextFieldCell: NSTextFieldCell {
// When the background changes (as a result of selection/deselection) switch appropriate colours
override var backgroundStyle: NSBackgroundStyle {
didSet {
if (backgroundStyle == NSBackgroundStyle.Dark) {
if self.textColor == NSColor.redColor() {
self.textColor = NSColor.yellowColor()
}
} else if (backgroundStyle == NSBackgroundStyle.Light) {
if (self.textColor == NSColor.yellowColor()) {
self.textColor = NSColor.redColor()
}
}
}
}
// When the colour changes, switch to a better alternative for the cell's current background
override var textColor: NSColor? {
didSet {
if let colour = self.textColor {
if backgroundStyle == NSBackgroundStyle.Dark {
if self.textColor == NSColor.redColor() {
self.textColor = NSColor.yellowColor()
}
} else if backgroundStyle == NSBackgroundStyle.Light {
if (self.textColor == NSColor.yellowColor()) {
self.textColor = NSColor.redColor()
}
}
}
}
}
}
If I set my CustomTextFieldCell as the custom class for my table view cell in the identity inspector, it works. I needed to add a property observer for textColor as well so that rows which are currently highlighted get the same treatment. I may alter it now so that it's not hard coded but this demonstrates the concept.
Thanks Ken.
If you use system colors like NSColor.labelColor, the text color invertion will happen automatically for selected cells. If you want to use another color for unselected text, you can use code like this in your custom NSTableCellView class:
override var backgroundStyle: NSView.BackgroundStyle {
willSet {
textField.textColor = newValue == .emphasized ? .labelColor : .secondaryLabelColor
}
}
In this Swift example, the selected text color is .labelColor, otherwise it is .secondaryLabelColor.

Resources