Sample code for creating a NSTextField "label"? - objective-c

In my desktop Mac OS X app, I'd like to programatically create a NSTextField "label" which has the same behavior and properties as a typical label created in Interface Builder.
I usually use (and very much like) IB, but in this case it must be done programatically.
Try as I might, I can't seem to find the combination of method calls that will programatically produce the same label-y behavior as a "Label" dragged from the IB View Library palette.
Can anyone provide or point out some example code of how to do this programatically? Thx.

A label is actually an instance of NSTextField, a subclass of NSView. So, since it is a NSView, it has to be added to another view.
Here's a working code:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSTextField *textField;
textField = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 10, 200, 17)];
[textField setStringValue:#"My Label"];
[textField setBezeled:NO];
[textField setDrawsBackground:NO];
[textField setEditable:NO];
[textField setSelectable:NO];
[view addSubview:textField];
}

macOS 10.12 and Later
Starting with macOS 10.12 (Sierra), there are three new NSTextField constructors:
NSTextField(labelWithString:), which the header file comment says “Creates a non-wrapping, non-editable, non-selectable text field that displays text in the default system font.”
NSTextField(wrappingLabelWithString:), which the header file comment says “Creates a wrapping, non-editable, selectable text field that displays text in the default system font.”
NSTextField(labelWithAttributedString:), which the header file comment says “Creates a non-editable, non-selectable text field that displays attributed text. The line break mode of this field is determined by the attributed string's NSParagraphStyle attribute.”
I tested the ones that take a plain (non-attributed string), and they create text fields that are similar to, but not precisely the same as, the text fields created in a storyboard or xib.
The important difference is that both constructors create a text field with textBackgroundColor (normally pure white) as its background color, while the storyboard text field uses controlColor (normally about 90% white).
Unimportantly, both constructors also set their fonts by calling NSFont.systemFont(ofSize: 0) (which produces a different NSFont object than my code below, but they wrap the same underlying Core Text font).
The wrappingLabelWithString: constructor sets the field's isSelectable to true. (This is documented in the header file.)
macOS 10.11 and Earlier
I compared four NSTextField instances: one created by dragging a “Label” to a storyboard, another created by dragging a “Wrapping Label” to a storyboard, and two in code. Then I carefully modified properties of the code-created labels until all their properties were exactly the same as the storyboard-created labels. These two methods are the result:
extension NSTextField {
/// Return an `NSTextField` configured exactly like one created by dragging a “Label” into a storyboard.
class func newLabel() -> NSTextField {
let label = NSTextField()
label.isEditable = false
label.isSelectable = false
label.textColor = .labelColor
label.backgroundColor = .controlColor
label.drawsBackground = false
label.isBezeled = false
label.alignment = .natural
label.font = NSFont.systemFont(ofSize: NSFont.systemFontSize(for: label.controlSize))
label.lineBreakMode = .byClipping
label.cell?.isScrollable = true
label.cell?.wraps = false
return label
}
/// Return an `NSTextField` configured exactly like one created by dragging a “Wrapping Label” into a storyboard.
class func newWrappingLabel() -> NSTextField {
let label = newLabel()
label.lineBreakMode = .byWordWrapping
label.cell?.isScrollable = false
label.cell?.wraps = true
return label
}
}
If you use one of these methods, don't forget to set your field's frame, or turn off its translatesAutoresizingMaskIntoConstraints and add constraints.
Here is the code I used to compare the different text fields, in case you want to check:
import Cocoa
class ViewController: NSViewController {
#IBOutlet var label: NSTextField!
#IBOutlet var multilineLabel: NSTextField!
override func loadView() {
super.loadView()
}
override func viewDidLoad() {
super.viewDidLoad()
let codeLabel = NSTextField.newLabel()
let codeMultilineLabel = NSTextField.newWrappingLabel()
let labels = [label!, codeLabel, multilineLabel!, codeMultilineLabel]
for keyPath in [
"editable",
"selectable",
"allowsEditingTextAttributes",
"importsGraphics",
"textColor",
"preferredMaxLayoutWidth",
"backgroundColor",
"drawsBackground",
"bezeled",
"bezelStyle",
"bordered",
"enabled",
"alignment",
"font",
"lineBreakMode",
"usesSingleLineMode",
"formatter",
"baseWritingDirection",
"allowsExpansionToolTips",
"controlSize",
"highlighted",
"continuous",
"cell.opaque",
"cell.controlTint",
"cell.backgroundStyle",
"cell.interiorBackgroundStyle",
"cell.scrollable",
"cell.truncatesLastVisibleLine",
"cell.wraps",
"cell.userInterfaceLayoutDirection"
] {
Swift.print(keyPath + " " + labels.map({ ($0.value(forKeyPath: keyPath) as? NSObject)?.description ?? "nil" }).joined(separator: " "))
}
}
}

This can be tricky to get right. I don't have the recipe for an exact replica handy, but when I've been stuck in a similar situation, here's what I do:
Create a UI element in IB.
Add an outlet to it from my controller class.
Break in gdb in awakeFromNib or whatever.
From the gdb prompt, "p *whateverOutlet" ... this will show you the C struct contents of the label NSTextField that IB set up.
By looking at all the myriad values in there, you can get a lot of guesses about what you're neglecting to set. Usually it ends up being some magic combination of bezel and border settings, that gets you where you want to be.

You could try using nib2objc to get all the properties that IB sets

Disassembled AppKit in Objective-C:
BOOL TMPSierraOrLater() {
static BOOL result = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
result = [NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){ 10, 12, 0 }];
});
return result;
}
#implementation NSTextField (TMP)
+ (instancetype)TMP_labelWithString:(NSString *)stringValue {
if (TMPSierraOrLater()) {
return [self labelWithString:stringValue];
}
NSParameterAssert(stringValue);
NSTextField *label = [NSTextField TMP_newBaseLabelWithoutTitle];
label.lineBreakMode = NSLineBreakByClipping;
label.selectable = NO;
[label setContentHuggingPriority:(NSLayoutPriorityDefaultLow + 1) forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentHuggingPriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
label.stringValue = stringValue;
[label sizeToFit];
return label;
}
+ (instancetype)TMP_wrappingLabelWithString:(NSString *)stringValue {
if (TMPSierraOrLater()) {
return [self wrappingLabelWithString:stringValue];
}
NSParameterAssert(stringValue);
NSTextField *label = [NSTextField TMP_newBaseLabelWithoutTitle];
label.lineBreakMode = NSLineBreakByWordWrapping;
label.selectable = YES;
[label setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentHuggingPriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
label.stringValue = stringValue;
label.preferredMaxLayoutWidth = 0;
[label sizeToFit];
return label;
}
+ (instancetype)TMP_labelWithAttributedString:(NSAttributedString *)attributedStringValue {
if (CRKSierraOrLater()) {
return [self labelWithAttributedString:attributedStringValue];
}
NSParameterAssert(attributedStringValue);
NSTextField *label = [NSTextField TMP_newBaseLabelWithoutTitle];
[label setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentHuggingPriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
label.attributedStringValue = attributedStringValue;
[label sizeToFit];
return label;
}
#pragma mark - Private API
+ (instancetype)TMP_newBaseLabelWithoutTitle {
NSTextField *label = [[self alloc] initWithFrame:CGRectZero];
label.textColor = NSColor.labelColor;
label.font = [NSFont systemFontOfSize:0.0];
label.alignment = NSTextAlignmentNatural;
label.baseWritingDirection = NSWritingDirectionNatural;
label.userInterfaceLayoutDirection = NSApp.userInterfaceLayoutDirection;
label.enabled = YES;
label.bezeled = NO;
label.bordered = NO;
label.drawsBackground = NO;
label.continuous = NO;
label.editable = NO;
return label;
}
#end

Specifically, you will want to setBordered:NO, and set the bezel style to whatever that bezel style is which I forgot. Also setEditable:NO, and optionally setSelectable:NO. That should suffice.

Related

UISearchBar text color change in iOS 7

How to change text color of UISearchBar in iOS 7?
In iOS 6, I was subclassing the UISearchBar and in layoutSubviews customising the properties of UITextField subview of UISearchBar.
But in iOS 7, UISearchBar doesn't have UITextField as its subview. How to fix this?
In iOS 7 to access Text Field you have to reiterate on level more. Change your code like this
for (UIView *subView in self.searchBar.subviews)
{
for (UIView *secondLevelSubview in subView.subviews){
if ([secondLevelSubview isKindOfClass:[UITextField class]])
{
UITextField *searchBarTextField = (UITextField *)secondLevelSubview;
//set font color here
searchBarTextField.textColor = [UIColor blackColor];
break;
}
}
}
Note : This is Not Public API
OR
You can use appearance Property of UIControls, Like
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setDefaultTextAttributes:#{NSForegroundColorAttributeName:[UIColor redColor]}];
Note: Appearance proxy can be used for iOS 9.0+
OutPut
You can set The tintcolor to apply to key elements in the search bar.
Use tintColor to tint foreground elements.
Use barTintColor to tint the bar background.
In iOS v7.0, all subclasses of UIView derive their behavior for tintColor from the base class. See the discussion of tintColor at the UIView level for more information.
Apple Doc
You can set the text colour by
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setTextColor:[UIColor blueColor]];
For XCode 6 (iOS8 SDK) the following DOESN'T work
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setTextColor:[UIColor redColor]];
But the following DOES work (for deployment to iOS7 and iOS8)
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setDefaultTextAttributes:#{NSForegroundColorAttributeName:[UIColor redColor]}];
While it's true that the UIAppearance protocol is a "public API," it's not true that UITextField supports this.
If you take a look at UITextField.h and look for the string "UI_APPEARANCE_SELECTOR" you'll see that it has no instances of this string. If you look at UIButton, you find quite a few - these are all of the properties that are officially supported by the UIAppearance API. It's somewhat well-known that UITextField is not supported by the UIAppearance API, so the code in Sandeep's answer will not always work and it's actually not the best approach.
This is a useful post with useful links: http://forums.xamarin.com/discussion/175/uitextfield-appearance
The correct approach is unfortunately messy - iterate through the subviews (or subviews of main subview for iOS7) and set it manually. Otherwise you will have unreliable results. But you can just create a category for UISearchBar and add a setTextColor:(UIColor*)color method. Example:
- (void)setTextColor:(UIColor*)color
{
for (UIView *v in self.subviews)
{
if([Environment isVersion7OrHigher]) //checks UIDevice#systemVersion
{
for(id subview in v.subviews)
{
if ([subview isKindOfClass:[UITextField class]])
{
((UITextField *)subview).textColor = color;
}
}
}
else
{
if ([v isKindOfClass:[UITextField class]])
{
((UITextField *)v).textColor = color;
}
}
}
}
Caution : This should lead to App Rejection!
KVC FTW. This did it for me.
UITextField *searchField = [self.searchBar valueForKey:#"_searchField"];
searchField.textColor = [UIColor redColor];
You can set the text attributes like so
[[UITextField appearanceWhenContainedIn:[<YOUR_CONTROLLER_NAME> class], nil] setDefaultTextAttributes:#{NSForegroundColorAttributeName:[UIColor whiteColor], NSFontAttributeName:[UIFont systemFontOfSize:14.0]}];
Here is working example done in C# using Xamarin:
SearchBar = new UISearchBar ();
foreach (var subView in SearchBar.Subviews) {
foreach (var field in subView.Subviews) {
if (field is UITextField) {
UITextField textField = (UITextField)field;
textField.TextColor = UIColor.White;
}
}
}
Hope this helps someone.
This seems to be the correct answer https://stackoverflow.com/a/19315895/2493073
The Swift version of it is:
UITextField.appearanceWhenContainedInInstancesOfClasses([UISearchBar.self]).textColor = UIColor.blueColor()
This would only work for iOS 9.0, in order to make it work for lower versions you'll need to follow this question. https://stackoverflow.com/a/27807417/2493073
Swift Extension
public extension UISearchBar {
public func setTextColor(color: UIColor) {
let svs = subviews.flatMap { $0.subviews }
guard let tf = (svs.filter { $0 is UITextField }).first as? UITextField else { return }
tf.textColor = color
}
}
In my case, I have multiple UISearchBar objects and they need to change the textField font color. The appearanceWhenContainedIn update one UISearchBar behavior, but another doesn't.
I subclass the UISearchBar and implement custom -(id)initWithFrame: as following, and it works.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.searchBarStyle = UISearchBarStyleMinimal;
self.tintColor = [UIColor whiteColor];
self.barTintColor = [UIColor whiteColor];
[[UITextField appearanceForTraitCollection:self.traitCollection whenContainedIn:[self class], nil] setDefaultTextAttributes:
#{
NSForegroundColorAttributeName : [UIColor whiteColor]
}];
}
return self;
}
UIAppearance Protocol Reference said that,
In other words, the containment statement in
appearanceWhenContainedIn: is treated as a partial ordering. Given a
concrete ordering (actual subview hierarchy), UIKit selects the
partial ordering that is the first unique match when reading the
actual hierarchy from the window down.
So, appearanceWhenContainedIn: won't deal with all UISearchBar in the hierachy of UIWindow. And it suggests that.
Use the appearanceForTraitCollection: and
appearanceForTraitCollection:whenContainedIn: methods to retrieve the
proxy for a class with the specified trait collection.
The easiest way to do it is by putting this code in viewDidAppear or viewWillAppear:
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setDefaultTextAttributes:#{NSForegroundColorAttributeName:[UIColor whiteColor]}];
This works in iOS 8 and Xcode 6, unlike some of the other code. It can mess around with the font and text size, etc, but you can change that in the text attributes.
That changes the text colour for all search bars in your app. If you only want to change one, use the above code, and then in any other views with a search bar, use the same code but set the colour to whatever you want.
you can use search bar inside textfield
UISearchBar * searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 50, 320, 44) ];
searchBar.autocorrectionType = UITextAutocapitalizationTypeWords;
searchBar.delegate = self;
searchBar.searchBarStyle = UISearchBarStyleMinimal;
searchBar.barTintColor = [UIColor redColor];
[[UITextField appearanceWhenContainedIn:[searchBar class], nil]setDefaultTextAttributes:#{NSForegroundColorAttributeName:[UIColor whiteColor]}];
[self.view addSubview:searchBar];
Update in Swift 3
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).textColor = UIColor.black
Even though I would have preferred to use appearance API, it didn't work with iOS8. Here's the least hackish solution I did come with:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.shouldEnableSearchBar)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
UITextField *searchBarTextField = [[AFPBaseViewController findSubviewsOfView:self.searchBar ofClass:[UITextField class]] firstObject];
searchBarTextField.textColor = AFPConstantsColorGold;
});
}
}
You could maybe even create a UIView category with this. The reason why this has to be called in viewDidAppear is that UISearchBar is actually contained in a ViewController, and doesn't load all its subviews until it has appeared on screen. It could be added into viewWillAppear too, but I haven't tested it.
+ (NSArray *)findSubviewsOfView:(UIView *)view ofClass:(Class)class
{
NSMutableArray *targetSubviews = [NSMutableArray new];
for (id subview in view.subviews)
{
if ([subview isKindOfClass:class])
{
[targetSubviews addObject:subview];
}
if ([subview subviews].count)
{
[targetSubviews addObjectsFromArray:[self findSubviewsOfView:subview ofClass:class]];
}
}
return targetSubviews.copy;
}
This is the right solution for iOS8:
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setDefaultTextAttributes:#{NSFontAttributeName: [UIFont fontWithName:#"Helvetica" size:14], NSForegroundColorAttributeName:[UIColor lightGrayColor]}];
You have to set the font as well, otherwise, the font size will be wrong.
This class will give you full control over every item in the UISearchBar
import UIKit
class SMTSearchBar: UISearchBar {
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
initialize()
}
convenience init() {
self.init(frame: CGRectZero)
initialize()
}
// Style the view
func initialize() {
// Search Area
let searchField = valueForKey("searchField") as! UITextField
searchField.textColor = Colors.White
searchField.font = UIFont(name: Fonts.MuseoSans500, size: 16)
searchField.backgroundColor = Colors.Black.colorWithAlphaComponent(0.1)
// Icons
let searchIcon = UIImage(named: Icons.Search)?.imageWithTint(Colors.White)
let smallClearIconNormal = UIImage(named: Icons.SmallClear)?.imageWithTint(Colors.White)
let smallClearIconHighLight = UIImage(named: Icons.SmallClear)?.imageWithTint(Colors.White.colorWithAlphaComponent(0.5))
setImage(searchIcon, forSearchBarIcon: .Search, state: .Normal)
setImage(smallClearIconHighLight, forSearchBarIcon: .Clear, state: .Highlighted)
setImage(smallClearIconNormal, forSearchBarIcon: .Clear, state: .Normal)
}
func setPlaceHolder(placeholder: String) {
for subView in subviews{
for subsubView in subView.subviews {
if let textField = subsubView as? UITextField {
textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [NSForegroundColorAttributeName: Colors.White.colorWithAlphaComponent(0.5)])
}
}
}
}
}
Usage (in navigation bar)
let searchBar:SMTSearchBar = SMTSearchBar()
searchBar.sizeToFit()
searchBar.setPlaceHolder("Search for cool things")
navigationItem.titleView = searchBar
searchBar.becomeFirstResponder()

How can I set [NSTextView selectedTextAttributes] on a background window?

The default value for [NSTextView selectedTextAttributes] is unusable in my app, because i allow the user to select colors (syntax highlighting) that are almost exactly the same as the background color.
I have written some math to determine a suitable color and can use this to set it:
textView.selectedTextAttributes = #{
NSBackgroundColorAttributeName: [NSColor yellowColor],
NSForegroundColorAttributeName: [NSColor redColor]
};
But when the window is in the background, it still uses the system default light grey.
I've attached screenshots of the above code with active vs inactive window. — how can I change the selected text background colour of the inactive window?
You can override the colour by overriding drawing method of NSLayoutManager.
final class LayoutManager1: NSLayoutManager {
override func fillBackgroundRectArray(rectArray: UnsafePointer<NSRect>, count rectCount: Int, forCharacterRange charRange: NSRange, color: NSColor) {
let color1 = color == NSColor.secondarySelectedControlColor() ? NSColor.redColor() : color
color1.setFill()
super.fillBackgroundRectArray(rectArray, count: rectCount, forCharacterRange: charRange, color: color1)
color.setFill()
}
}
And replace NSTextView's layout manager to it.
textView.textContainer!.replaceLayoutManager(layoutManager1)
Here's full working example.
As #Kyle asks for reason of setFill, I add some update.
From Apple manual:
... the charRange and color parameters are passed in merely for informational purposes; the color is
already set in the graphics state. If for any reason you modify it, you must restore it before
returning from this method. ...
Which means passing-in other color into super call has no effect, and you just need to
NSColor.setFill to make it work with super call.
Also, the manual requires to set it back to original one.
It's not when the window is in the background it's when the NSTextView is not selected. I don't think you can change that behavior.
You could create an attributed string and add the NSBackgroundColorAttributeName attribute to the range of the selected text when it loses focus. The attributed string stays the same color even when the focus is lost.
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:#"hello world"];
[string addAttribute:NSForegroundColorAttributeName value:[NSColor redColor] range:NSMakeRange(1, 7)];
[string addAttribute:NSBackgroundColorAttributeName value:[NSColor yellowColor] range:NSMakeRange(1, 7)];
[self.myTextView insertText:string];
EDIT by Abhi Beckert: this is how I implemented this answer (note I also had to disable the built in selected text attributes, or else they override the ones I'm setting):
#implementation MyTextView
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (!(self = [super initWithCoder:aDecoder]))
return nil;
// disable built in selected text attributes
self.selectedTextAttributes = #{};
return self;
}
- (id)initWithFrame:(NSRect)frameRect textContainer:(NSTextContainer *)container
{
if (!(self = [super initWithFrame:frameRect textContainer:container]))
return nil;
// disable built in selected text attributes
self.selectedTextAttributes = #{};
return self;
}
- (void)setSelectedRanges:(NSArray *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag
{
// remove from old ranges
for (NSValue *value in self.selectedRanges) {
if (value.rangeValue.length == 0)
continue;
[self.textStorage removeAttribute:NSBackgroundColorAttributeName range:value.rangeValue];
}
// apply to new ranges
for (NSValue *value in ranges) {
if (value.rangeValue.length == 0)
continue;
[self.textStorage addAttribute:NSBackgroundColorAttributeName value:[NSColor yellowColor] range:value.rangeValue];
}
[super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
}
#end
You can specify that your NSTextView should be treated as first responder by overriding layoutManagerOwnsFirstResponder(in:) from NSLayoutManager and the selection will use your defined attributes.
In Swift 5.1 would be:
override func layoutManagerOwnsFirstResponder(in window: NSWindow) -> Bool {
true
}

Custom NSTableCellView labels not changing text color when selected

I have a custom NSTableCellView with 3 textfields, 1 that came along and 2 others that i created myself. Here's the problem:
The textfields' text color stays the same even when i click on the row. I've tried to implement a code i found out by googling but it doesn't work. My Custom NSTableCellView code is:
- (void)drawRect:(NSRect)dirtyRect{
NSColor *color = [NSColor colorWithCalibratedRed:(26/255.0) green:(26/255.0) blue:(26/255.0) alpha:1.0];
[self.textField setTextColor:color];
color = [NSColor colorWithCalibratedRed:(102/255.0) green:(102/255.0) blue:(102/255.0) alpha:1.0];
[_lbl1 setTextColor:color];
[_lbl2 setTextColor:color];
}
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
NSColor *color = (backgroundStyle == NSBackgroundStyleDark) ? [NSColor windowBackgroundColor] : [NSColor controlShadowColor];
self.textField.textColor = color;
self.lbl1.textColor = color;
self.lbl2.textColor = color;
[super setBackgroundStyle:backgroundStyle];
}
What can i do to make the labels' text color white when the user clicks on them?
Actually, overriding setBackgroundStyle on NSTableViewCell has worked perfectly for me, at least on OS X 10.8. It is updated on selection events and on window activation/deactivation.
Here's my custom cell impl — as trivial as it can get:
#implementation RuntimeInstanceCellView
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
[super setBackgroundStyle:backgroundStyle];
self.detailTextField.textColor = (backgroundStyle == NSBackgroundStyleLight ? [NSColor darkGrayColor] : [NSColor colorWithCalibratedWhite:0.85 alpha:1.0]);
// self.detailTextField.textColor = (backgroundStyle == NSBackgroundStyleLight ? [NSColor blackColor] : [NSColor whiteColor]);
}
#end
Expanding on the accepted answer, in Swift 2.0 the process is slightly different. Override the backgroundStyle property of your NSTableCellView subclass to add a didSet property observer:
class CustomTableCellView: NSTableCellView {
#IBOutlet weak var detailTextField: NSTextField!
override var backgroundStyle: NSBackgroundStyle {
didSet {
if self.backgroundStyle == .Light {
self.detailTextField.textColor = NSColor.controlTextColor()
} else if self.backgroundStyle == .Dark {
self.detailTextField.textColor = NSColor.alternateSelectedControlTextColor()
}
}
}
}
And for Swift 3 & 4 (isn’t this fun?):
override var backgroundStyle: NSView.BackgroundStyle {
didSet {
if self.backgroundStyle == .light {
self.detailTextField.textColor = NSColor.controlTextColor
} else if self.backgroundStyle == .dark {
self.detailTextField.textColor = NSColor.alternateSelectedControlTextColor
}
}
}
In your tableViewSelectionDidChange get the cell using
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath]; //replace UITableViewCell with your customCell class name if it other
//now as u got the instance of your cell u can modify the labels in it, like
cell.lable1.textColor = [UIColor whiteColor];
This will work for you.
You may get problem when you select other cell again after this, at that time previous cell may have still white colored labels. If this causes problems to you just have a NSIndexPath instance in your header class which represents previous selected indexPath, using this you can set back to default colors after selecting a new cell.

How to add custom font to UILabel but have label retain its size from storyboard

I currently am subclassing a UILabel to change the font to a custom font, however I would also like it to retain the size that I have set in storyboard for each label. Is there a method of doing this and also detect the current chosen style bold etc and replacing it with the relevant custom font if possible?
Here is the code I use to set the current font.
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
self.font = [UIFont fontWithName:#"FrutigerLT-Roman" size:17.0];
}
return self;
}
To add a custom font, to your app, check the following link: http://shang-liang.com/blog/custom-fonts-in-ios4/
Now, to keep the size set in the storyboard, it should be fine:
self.font = [UIFont fontWithName:#"FrutigerLT-Roman" size:self.font.pointSize];
In the end I wrote my own solution.
https://stackoverflow.com/a/12281017/1565615 #Nathan R. helped me with getting the font size.
Then I extracted the font-weight component of the UIFont description and changed the font accordingly this is great with custom font's as now I can set the font size and style in storyboard and it will set that within the subclassed version of the UILabel.
I wish there was an easier way to identify the type of font-weight being used like font.fontWeight I realise that my solution is longwinded but it works any further ideas would be useful.
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self)
{
NSString *fontInfo = self.font.description;//Complete font description
NSArray *splitUpFontDescription = [fontInfo componentsSeparatedByString: #";"];//Split up
NSString *fontWeight = [[NSString alloc]init];
for (NSString *tempString in splitUpFontDescription)
{
if ([tempString rangeOfString:#"font-weight"].location != NSNotFound)//Font weight found
{
fontWeight = [tempString stringByReplacingOccurrencesOfString:#" "
withString:#""];//Remove whitespace
fontWeight = [fontWeight stringByReplacingOccurrencesOfString:#"font-weight:"
withString:#""];
}
}
NSLog(#"Font style (Weight) = *%#*",fontWeight);
if ([fontWeight isEqualToString:#"normal"])
{
//Set to custom font normal.
self.font = [UIFont fontWithName:#"FrutigerLT-Roman" size:self.font.pointSize];
}
else if([fontWeight isEqualToString:#"bold"])
{
//Set to custom font bold.
self.font = [UIFont fontWithName:#"FrutigerLT-Bold" size:self.font.pointSize];
}
}
return self;
}

Why do all backgrounds disappear on UITableViewCell select?

My current project's UITableViewCell behavior is baffling me. I have a fairly straightforward subclass of UITableViewCell. It adds a few extra elements to the base view (via [self.contentView addSubview:...] and sets background colors on the elements to have them look like black and grey rectangular boxes.
Because the background of the entire table has this concrete-like texture image, each cell's background needs to be transparent, even when selected, but in that case it should darken a bit. I've set a custom semi-transparent selected background to achieve this effect:
UIView *background = [[[UIView alloc] initWithFrame:self.bounds] autorelease];
background.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
background.opaque = NO;
[self setSelectedBackgroundView:background];
And although that yields the right look for the background, a weird side effect happens when I select the cell; all other backgrounds are somehow turnt off. Here's a screenshot. The bottom cell looks like it should and is not selected. The top cell is selected, but it should display the black and grey rectangular areas, yet they are gone!
Who knows what's going on here and even more important: how can I correct this?
What is happening is that each subview inside the TableViewCell will receive the setSelected and setHighlighted methods. The setSelected method will remove background colors but if you set it for the selected state it will be corrected.
For example if those are UILabels added as subviews in your customized cell, then you can add this to the setSelected method of your TableViewCell implementation code:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
self.textLabel.backgroundColor = [UIColor blackColor];
}
where self.textLabel would be whatever those labels are that are shown in the picture above
I'm not sure where your adding your selected view, I usually add it in the setSelected method.
Alternatively, you can subclass the UILabel and override the setHighlighted method like so:
-(void)setHighlighted:(BOOL)highlighted
{
[self setBackgroundColor:[UIColor blackColor]];
}
The cell highlighting process can seem complex and confusing if you don't know whats going on. I was thoroughly confused and did some extensive experimentation. Here's the notes on my findings that may help somebody (if anyone has anything to add to this or refute then please comment and I will endeavour to confirm and update)
In the normal “not selected” state
The contentView (whats in your XIB unless you coded it otherwise) is drawn normally
The selectedBackgroundView is HIDDEN
The backgroundView is visible (so provided your contentView is transparent you see the backgroundView or (if you have not defined a backgroundView you'll see the background colour of the UITableView itself)
A cell is selected, the following occurs immediately with-OUT any animation:
All views/subviews within the contentView have their backgroundColor cleared (or set to transparent), label etc text color's change to their selected colour
The selectedBackgroundView becomes visible (this view is always the full size of the cell (a custom frame is ignored, use a subview if you need to). Also note the backgroundColor of subViews are not displayed for some reason, perhaps they're set transparent like the contentView). If you didn't define a selectedBackgroundView then Cocoa will create/insert the blue (or gray) gradient background and display this for you)
The backgroundView is unchanged
When the cell is deselected, an animation to remove the highlighting starts:
The selectedBackgroundView alpha property is animated from 1.0 (fully opaque) to 0.0 (fully transparent).
The backgroundView is again unchanged (so the animation looks like a crossfade between selectedBackgroundView and backgroundView)
ONLY ONCE the animation has finished does the contentView get redrawn in the "not-selected" state and its subview backgroundColor's become visible again (this can cause your animation to look horrible so it is advisable that you don't use UIView.backgroundColor in your contentView)
CONCLUSIONS:
If you need a backgroundColor to persist through out the highlight animation, don't use the backgroundColor property of UIView instead you can try (probably with-in tableview:cellForRowAtIndexPath:):
A CALayer with a background color:
UIColor *bgColor = [UIColor greenColor];
CALayer* layer = [CALayer layer];
layer.frame = viewThatRequiresBGColor.bounds;
layer.backgroundColor = bgColor.CGColor;
[cell.viewThatRequiresBGColor.layer addSublayer:layer];
or a CAGradientLayer:
UIColor *startColor = [UIColor redColor];
UIColor *endColor = [UIColor purpleColor];
CAGradientLayer* gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = viewThatRequiresBGColor.bounds;
gradientLayer.colors = #[(id)startColor.CGColor, (id)endColor.CGColor];
gradientLayer.locations = #[[NSNumber numberWithFloat:0],[NSNumber numberWithFloat:1]];
[cell.viewThatRequiresBGColor.layer addSublayer:gradientLayer];
I've also used a CALayer.border technique to provide a custom UITableView seperator:
// We have to use the borderColor/Width as opposed to just setting the
// backgroundColor else the view becomes transparent and disappears during
// the cell's selected/highlighted animation
UIView *separatorView = [[UIView alloc] initWithFrame:CGRectMake(0, 43, 1024, 1)];
separatorView.layer.borderColor = [UIColor redColor].CGColor;
separatorView.layer.borderWidth = 1.0;
[cell.contentView addSubview:separatorView];
When you start dragging a UITableViewCell, it calls setBackgroundColor: on its subviews with a 0-alpha color. I worked around this by subclassing UIView and overriding setBackgroundColor: to ignore requests with 0-alpha colors. It feels hacky, but it's cleaner than any of the other solutions I've come across.
#implementation NonDisappearingView
-(void)setBackgroundColor:(UIColor *)backgroundColor {
CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
if (alpha != 0) {
[super setBackgroundColor:backgroundColor];
}
}
#end
Then, I add a NonDisappearingView to my cell and add other subviews to it:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
UIView *background = [cell viewWithTag:backgroundTag];
if (background == nil) {
background = [[NonDisappearingView alloc] initWithFrame:backgroundFrame];
background.tag = backgroundTag;
background.backgroundColor = backgroundColor;
[cell addSubview:background];
}
// add other views as subviews of background
...
}
return cell;
}
Alternatively, you could make cell.contentView an instance of NonDisappearingView.
My solution is saving the backgroundColor and restoring it after the super call.
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
UIColor *bgColor = self.textLabel.backgroundColor;
[super setSelected:selected animated:animated];
self.textLabel.backgroundColor = bgColor;
}
You also need to do the same thing with -setHighlighted:animated:.
Found a pretty elegant solution instead of messing with the tableView methods. You can create a subclass of UIView that ignores setting its background color to clear color. Code:
class NeverClearView: UIView {
override var backgroundColor: UIColor? {
didSet {
if UIColor.clearColor().isEqual(backgroundColor) {
backgroundColor = oldValue
}
}
}
}
Obj-C version would be similar, the main thing here is the idea
I created a UITableViewCell category/extension that allows you to turn on and off this transparency "feature".
You can find KeepBackgroundCell on GitHub
Install it via CocoaPods by adding the following line to your Podfile:
pod 'KeepBackgroundCell'
Usage:
Swift
let cell = <Initialize Cell>
cell.keepSubviewBackground = true // Turn transparency "feature" off
cell.keepSubviewBackground = false // Leave transparency "feature" on
Objective-C
UITableViewCell* cell = <Initialize Cell>
cell.keepSubviewBackground = YES; // Turn transparency "feature" off
cell.keepSubviewBackground = NO; // Leave transparency "feature" on
Having read through all the existing answers, came up with an elegant solution using Swift by only subclassing UITableViewCell.
extension UIView {
func iterateSubViews(block: ((view: UIView) -> Void)) {
for subview in self.subviews {
block(view: subview)
subview.iterateSubViews(block)
}
}
}
class CustomTableViewCell: UITableViewCell {
var keepSubViewsBackgroundColorOnSelection = false
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
// MARK: Overrides
override func setSelected(selected: Bool, animated: Bool) {
if self.keepSubViewsBackgroundColorOnSelection {
var bgColors = [UIView: UIColor]()
self.contentView.iterateSubViews() { (view) in
guard let bgColor = view.backgroundColor else {
return
}
bgColors[view] = bgColor
}
super.setSelected(selected, animated: animated)
for (view, backgroundColor) in bgColors {
view.backgroundColor = backgroundColor
}
} else {
super.setSelected(selected, animated: animated)
}
}
override func setHighlighted(highlighted: Bool, animated: Bool) {
if self.keepSubViewsBackgroundColorOnSelection {
var bgColors = [UIView: UIColor]()
self.contentView.iterateSubViews() { (view) in
guard let bgColor = view.backgroundColor else {
return
}
bgColors[view] = bgColor
}
super.setHighlighted(highlighted, animated: animated)
for (view, backgroundColor) in bgColors {
view.backgroundColor = backgroundColor
}
} else {
super.setHighlighted(highlighted, animated: animated)
}
}
}
All we need is to override the setSelected method and change the selectedBackgroundView for the tableViewCell in the custom tableViewCell class.
We need to add the backgroundview for the tableViewCell in cellForRowAtIndexPath method.
lCell.selectedBackgroundView = [[UIView alloc] init];
Next I have overridden the setSelected method as mentioned below.
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
UIImageView *lBalloonView = [self viewWithTag:102];
[lBalloonView setBackgroundColor:[[UIColor hs_globalTint] colorWithAlphaComponent:0.2]];
UITextView *lMessageTextView = [self viewWithTag:103];
lMessageTextView.backgroundColor = [UIColor clearColor];
UILabel *lTimeLabel = [self viewWithTag:104];
lTimeLabel.backgroundColor = [UIColor clearColor];
}
Also one of the most important point to be noted is to change the tableViewCell selection style. It should not be UITableViewCellSelectionStyleNone.
lTableViewCell.selectionStyle = UITableViewCellSelectionStyleGray;