Custom UIControl with UILabel dimming on tint color change - cocoa-touch

In case of UISegmentedControl, once a popover or alert is present, the Control dims to grey (desaturates the tint color)
I'am building my own UIControl subclass, which uses a UILabel as a subview
i want to dim (desaturate) the text color of the UILabel, same way as by UISegmentedControl or (UIButton...)

Look at the tintColor and tintAdjustmentMode properties on UIView (available since iOS 7) and the tintColorDidChange method.
If you override them in your custom view you can respond to being dimmed out.
As the iOS 7 UI Transitioning Guide says:
When an alert or action sheet appears, iOS 7 automatically dims the tint color of the views behind it. To respond to this color change, a custom view subclass that uses tintColor in its rendering should override tintColorDidChange to refresh the rendering when appropriate.
The solution may look like this :
- (void)tintColorDidChange {
self.titleLabel.textColor = self.tintColor;
}

While the accepted answer did help me, the result was that the dimmed color was applied to my control even when the screen was not dimmed. I fixed this in the following manner:
override func tintColorDidChange() {
switch tintAdjustmentMode {
case .Dimmed:
myLabel.textColor = UIColor.grayColor()
default:
myLabel.textColor = UIColor.blueColor()
}
}
This correctly applies a gray color to the control only if the screen is dimmed.

Related

Custom NSView background color not changing when switching in and out of dark mode

I cannot figure out how to update the background color of my custom NSView when the user switches in and out of dark mode.
I've read the documentation and followed instructions here:detecting darkmode
The strange thing is that I can get all the subviews to behave correctly, but for some strange reason I can't get the background color of the main view to change. The background color of the view looks correct when I start the app in either mode, but when I switch between modes while the app is running it doesn't update to the new theme.
Would be grateful for any suggestions.
Inside the Custom NSView I have the method
- (void) viewDidChangeEffectiveAppearance
{
self.needsDisplay = YES;
}
and inside the drawRect I have a do a simple color change before continuing with drawing in the view
NSAppearance *currentAppearance = [NSAppearance currentAppearance];
if (#available(*, macOS 10.14)) {
if(currentAppearance.name == NSAppearanceNameDarkAqua) {
red = 0.5*red+0.5;
green = 0.5*green+0.5;
blue = 0.5*blue+0.5;
}
}
Here is a screenshot of darkmode before (the way it should look)
Dark Mode Before
Here is a screenshot of light mode after user switch
Light Mode After
Here is a screenshot of lightmode before (the way it should look)
Light Mode Before
And here is a screenshot of darkmode after user switch
Dark Mode After
ps The reason I'm baffled and have little code to post is that the correct behavior is supposed to happen automatically with little effort. I even deleted the view from the nib and rebuilt it thinking maybe some setting got corrupted, but that didn't solve the problem.
Update: I found the source of the problem. This method gets called in windowDidLoad
- (void) setTransparent:(BOOL)transparent
{
if(transparent) {
[self.window setOpaque:NO];
NSColor *backgroundColor = [NSColor windowBackgroundColor];
backgroundColor = [backgroundColor colorWithAlphaComponent: .75];
[self.window setBackgroundColor:backgroundColor];
self.window.alphaValue = 0.75;
}
else {
[self.window setOpaque:YES];
NSColor *backgroundColor = [NSColor windowBackgroundColor];
backgroundColor = [backgroundColor colorWithAlphaComponent: 1];
[self.window setBackgroundColor:backgroundColor];
self.window.alphaValue = 1;
}
}
I get the expected behavior if I comment out the call to this method.
Why did this cause me to lose the automatic behavior of background color change when the user changes between light and dark mode?
My guess is that you’re not actually using the standard color for the background color.
You are using [NSColor windowBackgroundColor], however then making a copy with a different alpha component (via colorWithAlphaComponent), making it no longer a standard color.
My guess is a lot of the automatic ‘just works’ behaviour happens when you use the standard color definitions. As a test, could you try removing the colorWithAlphaComponent calls (where you are adding the transparency) from your settransparent method and see whether it works? if it does, you might need to find another way to add transparency to your view if you want the automatic behaviour.

Cursor invisible in UISearchBar iOS 7

I have UISearchBar in UITableView as a table header. When I push the UISearchBar for start searching, this method is being triggered
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
for UISearchDisplayController.
But result is like that;
As you can see, there is no cursor, I can start typing and search, everything works fine. Also it's invisible only in iOS 7. However, with iOS 6.1 and iOS 7.1 Beta 3 I could see the cursor. So how can I make UISearchBar cursor visible or how can I add cursor in my UISearchBar?
Cursor in the search bar takes color from Search Bar -> View -> Tint Color property.
In your case, it is set to White color, so it becomes invisible.
try using with
-(void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller
{
self.navigationItem.titleView.tintColor = [UIColor blueColor];
}
hope this will help you
Try setting text field tint color using UIAppearance
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setTintColor: [UIColor darkGrayColor]];
If you want the cursor and cancel button to be different colors...
Start by setting the view's tint color (not the bar tint) in the storyboard editor, which will be applied to both the cursor and cancel button.
To make the cursor a different color, you need to do it programatically. The text field is nested a couple levels down in the searchBar's subviews. Use this setTextFieldTintColor helper function to traverse all of the subviews.
#IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
// set tint color for all subviews in searchBar that are of type UITextField
setTextFieldTintColor(to: UIColor.darkText, for: searchBar)
}
func setTextFieldTintColor(to color: UIColor, for view: UIView) {
if view is UITextField {
view.tintColor = color
}
for subview in view.subviews {
setTextFieldTintColor(to: color, for: subview)
}
}
The end result looks like this:
Try this it's only one line of code to solve your problem , Change cursor tintColor property white to blue.
searchBar.tintColor = [UIColor blueColor];
Hope this will help to someone .
Your cursor is white because your tintColor property on UISearchBar is set to white.
If you want Cancel btn to be white, but cursor to be black you can use:
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).tintColor = .black

iOS7 UILabel to adopt same tintColor as window

I know that for elements of classes UIButton and UIBarButtonItem they automatically assume window.tintColor as the main colour, which results of an immediate change in case I set a new tintColor to window at any time in the app.
I was wondering if there is any way to make UILabel elements to follow the same pattern, where once created they automatically assumer its default colour as window.tintColor and if changing window.tintColor at any time within my app runtime would also result in changing the UILabel tintColour automatically?
I hope that makes sense.
UILabels are a subclass of UIView, so when you are running in iOS 7 they will have a tintColor property and will inherit that color from their parent view if their tint color is set to nil (which is default).
From Apple's Documentation:
By default, a view’s tint color is nil, which means that the view uses its parent’s tint. It also means that when you ask a view for its tint color, it always returns a color value, even if you haven’t set one.
However, you also ask "if changing window.tintColor at any time within my app runtime would also result in changing the UILabel tintColour automatically?" Apple advises you to not change the tint color when items are on screen:
In general, it’s best to change a view’s tint color while the view is offscreen.
I would guess this is because there is no guarentee that all the various UI elements will detect the tintColor change and update their visible views. However, the UIView documentation suggests a workaround if you want to update tintColor while your UILables are on screen:
To refresh subview rendering when this property changes, override the tintColorDidChange method.
So just make sure to call tintColorDidChange on any views currently on screen whose tint color should update when the tintColor of their parent view changes.
But why don't your UILabel's update their color?
So the above helps you set and update your various tintColor's, but you're not seeing any effect - why?
Well that has to do with what Apple designed Tint to indicate. From the Human Interface Guidelines:
color gives users a strong visual indicator of interactivity
Apple got rid of borders and gradients around interactive elements and replaced them with color - specifically tintColor. The whole idea behind tintColor is that things users can tap on get it, and things they can't tap on don't.
UILabel is not an interactive element - it is a text description - and so Apple will let you set a tintColor on it (as any UIView has a tintColor) but setting that tintColor will not change how it is drawn.
So what should you do? First, be aware that making more than just buttons take on the tint color could be a poor UI choice for your app - iOS 7 users and Apple app reviewers both will be expecting those rules to be followed.
So are you forced to keep your UILabel free from color then?
No - especially if you do things "right". Apple explains:
In a content area, add a button border or background only if necessary. Buttons in bars, action sheets, and alerts don’t need borders because users know that most of the items in these areas are interactive. In a content area, on the other hand, a button might need a border or a background to distinguish it from the rest of the content.
I would suggest you consider the UI of your app. If you really want your non-intereactive elements to have the same tintColor as your interactive elements, then make sure you use something more, like a border or background color, so your users (and Apple app reviewers) know what is clickable and what is not.
As to how you should update the colors, you can either manually set the textColor property to be whatever color you want, or you'll need to make your own subclass of UILabel that overrides - (void)tintColorDidChange to update the textColor property when notifications are sent out - allowing you to have a UILabel whose text updates to match the tintColor of its parent.
I hope this helps!
Found above explanation to be helpful - particularly the pointer to tintColorDidChange(). However, getting it to work out right didn't work at first, but finally came up with a code example that does. Basically, I had a tableView with cells containing images and labels. The images would update with a change in tintColor, but not the labels - which didn't look right. Better for either both to change or neither change. The code below is for the cell in the tableView. This was written with Swift 4.2.
//
// ImageLabelCell.swift
// -- provides an example of changing "tintColor" of UILabel to behave like other elements.
// -- made problem a bit more complex by having a "selected" cell be in inverse colors - so also deal with background.
//
import UIKit
enum CellTypes: Int, CaseIterable { case cell1, cell2, cell3, cell4
// This type provides a demonstration of a way to associate different titles and images to populate the UILabel and UIImageView in the cell.
var title: String {
return "\(self)".uppercased()
}
var imageName: String {
return "\(self)"
}
}
class ImageLabelCell: UITableViewCell {
#IBOutlet weak var lblString: UILabel?
#IBOutlet weak var imgView: UIImageView!
fileprivate var type = CellTypes.cell1
fileprivate var cellSelected = false
}
extension ImageLabelCell {
func getFgBgColors() -> (UIColor, UIColor) { // get foreground, background colors
let white = UIColor.white
var useTint = UIColor.blue // Use your app color here. Just ensures useTint is not nil.
if let tint = tintColor {
useTint = tint
}
if cellSelected {
return (white, useTint) // Selected cell is white on colored background
} else {
return (useTint, white) // Unselected cell is colored on white background
}
}
func configureCell(type: CellTypes, andSelected selected: Bool) {
// Save properties we may use again later
self.type = type
self.cellSelected = selected
// Set label text and image
lblString?.text = type.title
imgView.image = UIImage(named: type.imageName)
// Set colors
let (fgColor, bgColor) = getFgBgColors()
imgView.tintColor = fgColor
self.contentView.backgroundColor = bgColor
lblString?.textColor = fgColor
}
override func tintColorDidChange() {
// This gets called when the program tint color changes to gray (or back again) such as when popups appear/disappear.
let (fgColor, bgColor) = getFgBgColors()
// NOTE: need to set text color and background color. Imageview can take care of itself.
lblString?.textColor = fgColor
self.contentView.backgroundColor = bgColor
}
}
Simple solution may work for some usecases - use let fakeButton = UIButton(type: .system) which automatically adjust to window.tintColor.
Important thing is to set .system type which automatically match the window.tintColor.
You may also set fakeButton.isEnabled = false to prevent user interacting with the button. However, we didn't set target-action so the button is already the fake one.

How to change the background color of an NSPopupButton?

I am trying to tackle a problem which sounds pretty simple: changing the background color of an NSPopupButton.
Interface Builder only allows changing the style to a pre-defined one and doesn't allow changing the background color. Also, setting up an IBOutlet didn't help since NSPopupButton doesn't have a setBackgroundColor method.
I also tried subclassing NSPopupButton to override the drawRect method. Here's what I have tried:
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor redColor] setFill];
NSRectFill(dirtyRect);
}
This draws a red rectangle over the NSPopupButton rather than setting it as a background color.
Any ideas on how to go about solving this?
You should create a subclass of NSPopUpButtonCell, then override
- (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView
NSPopupButtonCell is a subclass of NSButtonCell which defines several methods for drawing individual cell components, eg bezel, title, image.
You can then expand the NSPopupButton and change its cell subclass to your new subclass and it should use your drawing methods.
Cocoa primarily uses NSCell to handle drawing, unlike iOS
Swift version of #DanBrooker's answer. The example shows setting a background color
class PopUpButtonCell: NSPopUpButtonCell {
override func drawBezel(withFrame frame: NSRect, in controlView: NSView) {
guard let context = NSGraphicsContext.current?.cgContext else { return }
NSColor.red.setFill() // NSColor.white.setFill()
context.fill(frame)
}
}
The button is draw by the system, so there isn't a real way to set the background color in a way that the system draws it like you want.The only thing that you can do is to draw it in the drawRect method, also drawing the title, and drawing a portion of the rectangle.

UINavigationBar tint color flashing in iOS 4

The app I'm working on has a custom nab bar but supports iOS 4.2-iOS 5, so I need to set the UINavigationBar background and tint in this old school way in my app delegate.
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
self.tintColor = [UIColor colorWithRed:42.0/255.0
green:164.0/255.0
blue:182.0/255.0
alpha:1.0];
UIImage *img = [UIImage imageNamed:#"navbar_bg.png"];
[img drawInRect:CGRectMake(0.0, 0.0,
self.frame.size.width,
self.frame.size.height)];
}
#end
This works for the most part, but I noticed when the app is first starting, the UIBarButtonItems flash the default navigation bar color for a second before they correct themselves and change color to match the navigation bar. Interestingly, the navigation bar itself uses the background image correctly from the get-go.
To be clear, I'm using setBackgroundImage for UINavigationBar on iOS 5 devices which works as expected so the flash is only in iOS 4.
Anyone have any insight on why this would happen and/or how to fix it?
The bar button items are the wrong color? You can manually set their tint color in viewDidLoad: to the tint color
navigationBar.rightBarButtonItem.tintColor = [UIColor ...]
if you're using a nib file. Otherwise you can do the same thing in loadView: . Either way this code will get executed as part of the initial draw loop so you'll have the proper color without any flashing.
Also for future reference, it's technically incorrect to override a method inside a category. (The latest version of Xcode, 4.3, will give you a warning about this). You should either properly subclass UINavigationBar or do "method swizzling". But that's pretty tough so don't worry about it right now :)
If you call the class with the code referenced in viewDidLoad try moving it to awakeFromNib