I have an NSText field in MainMenu.xib and I have an action set to validate it for an email address. I want the NSTexFields border color (That blue glow) to be red when my action returns NO and green when the action returns YES. Here is the action:
-(BOOL) validEmail:(NSString*) emailString {
NSString *regExPattern = #"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,4}$";
NSRegularExpression *regEx = [[NSRegularExpression alloc] initWithPattern:regExPattern options:NSRegularExpressionCaseInsensitive error:nil];
NSUInteger regExMatches = [regEx numberOfMatchesInString:emailString options:0 range:NSMakeRange(0, [emailString length])];
NSLog(#"%ld", regExMatches);
if (regExMatches == 0) {
return NO;
} else
return YES;
}
I call this function and setting the text-color right now, but I would like to set the NSTextField's glow color instead.
- (void) controlTextDidChange:(NSNotification *)obj{
if ([obj object] == emailS) {
if ([self validEmail:[[obj object] stringValue]]) {
[[obj object] setTextColor:[NSColor colorWithSRGBRed:0 green:.59 blue:0 alpha:1.0]];
[reviewButton setEnabled:YES];
} else {
[[obj object] setTextColor:[NSColor colorWithSRGBRed:.59 green:0 blue:0 alpha:1.0]];
[reviewButton setEnabled:NO];
}
}
}
I am open to sub-classing NSTextField, but the cleanest way to do this would be greatly appreciated!
My solution for Swift 2 is
#IBOutlet weak var textField: NSTextField!
...
// === set border to red ===
// if text field focused right now
NSApplication.sharedApplication().mainWindow?.makeFirstResponder(self)
// disable following focusing
textField.focusRingType = .None
// enable layer
textField.wantsLayer = true
// change border color
textField.layer?.borderColor = NSColor.redColor().CGColor
// set border width
textField.layer?.borderWidth = 1
The way to go about this is to subclass NSTextFieldCell and draw your own focus ring. As far as I can tell there's no way to tell the system to draw the focus ring using your own color, so you'll have to call setFocusRingType:NSFocusRingTypeNone, check if the control has first responder status in your drawing method, and if so draw a focus ring using your own color.
If you decide to use this approach remember that the focus ring is a user defined style (blue or graphite), and there's no guarantee future versions of OSX won't allow the user to change the standard color to red or green. It's also likely the focus ring drawing style will change in future versions of OSX, at which point you'll have to update your drawing code in order for things to look right.
Related
I have a custom NSButton, but no matter what i do, the disabled color is always gray
I tried all solutions i came across
i'am setting the attributed string title with white foreground color (i looks like the color attribute is ignored for the disabled state)
i did set [[self cell] setImageDimsWhenDisabled:NO];
event when the documentations states
// When disabled, the image and text of an NSButtonCell are normally dimmed with gray.
// Radio buttons and switches use (imageDimsWhenDisabled == NO) so only their text is dimmed.
#property BOOL imageDimsWhenDisabled;
it doesn't work
My NSButton uses wantsUpdateLayer YES, so the draw methods are overwritten, but i don't understand, where the title is drawn
On OS X 10.9 I've managed to alter the color of the button's text when it's disabled by sub-classing the cell that draws the button.
Create a new NSButtonCell subclass in Xcode and override the following method:
- (NSRect)drawTitle:(NSAttributedString *)title
withFrame:(NSRect)frame
inView:(NSView *)controlView {
NSDictionary *attributes = [title attributesAtIndex:0 effectiveRange:nil];
NSColor *systemDisabled = [NSColor colorWithCatalogName:#"System"
colorName:#"disabledControlTextColor"];
NSColor *buttonTextColor = attributes[NSForegroundColorAttributeName];
if (systemDisabled == buttonTextColor) {
NSMutableDictionary *newAttrs = [attributes mutableCopy];
newAttrs[NSForegroundColorAttributeName] = [NSColor orangeColor];
title = [[NSAttributedString alloc] initWithString:title.string
attributes:newAttrs];
}
return [super drawTitle:title
withFrame:frame
inView:controlView];
}
Select the button in Xcode, then select its cell (maybe easiest to do this in the Interface Builder dock), now got to the Identity Inspector and set the cell's class to that of your subclass.
This is because of the default true value of
- (BOOL)_shouldDrawTextWithDisabledAppearance
Try to change this method instead of imageDimsWhenDisabled. If you are using Swift 4, I would do the following in the Bridging header:
#import <AppKit/AppKit.h>
#interface NSButtonCell (Private)
- (BOOL)_shouldDrawTextWithDisabledAppearance;
#end
And in the subclass of NSButtonCell:
override func _shouldDrawTextWithDisabledAppearance() -> Bool {
return false
}
And that's it: the grey should disappear
Dear fellow Cocoa programmers,
What I'd like to accomplish:
I have a checkbox, a popUpButton(which is hidden) and a NSView on my canvas.
If myCheckbox is checked -> show the popUpButton and draw a line through bezierPath on the NSView.
if myCheckbox is UNchecked -> Hide the popUpButton again and "undraw" the path
The code:
- (IBAction)isChecked:(id)sender {
//if myChekcbox is checked, show the pop up button
if ([sender state]==NSOnState) {
NSLog(#"Checked");
[myPopUp setHidden:NO];
}
else
{
//if the checkbox is unchecked, hide the popupbutton
[myPopUp setHidden:YES];
NSLog(#"Unchecked");
}
//reload my drawrect method (reload the view)
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)dirtyRect
{
//if the checkedbutton is checked, draw the line
if ([myCheckbox state]==NSOnState)
{
NSBezierPath *myPath = [NSBezierPath bezierPath];
[myPath moveToPoint:NSMakePoint(10, 20)];
[myPath lineToPoint:NSMakePoint(50, 20)];
[myPath setLineWidth:2];
[myPath stroke];
}
}
The problem:
if checked state = NSOnState the popUpButton is visible but the line just won't draw and I wonder why... I personally think it's a connection(s) problem.
I uploaded the project file (it's rather small-35kb) here:Drawing.zip
Globally:
I've read the NSView documentation and it's saying there is only one way to draw to a view and it's through the drawRect method. Is this actually true? Also is this a descent way to draw to a view? (if function in the view and setNeedsDisplay:YES in the method)
thanks in advance,
Ben
You will need to get an NSColor instance and then call setStroke on it to set a current stroke color. It does not know which color to use to stroke the path at the start of drawRect:, so you have to tell it.
In a view-based NSTableView, your custom row and cell views (subclasses of NSTableRowView and NSTableCellView) get their backgroundStyle property set, so you know if the background is light or predominantly dark (for the selected, highlighted row).
This even gets passed to immediate subviews.
Now, the default text label of the table cell view reacts correctly to this, so on a dark background, the text is drawn in a suitable light color.
However, an NSTextField added to provide extra text (with a custom text color set in Interface Builder) does not automatically adhere to this convention.
Is there a simple way in the API to get the text field to play nice, or do I have to subclass it?
Instead of overriding drawRect, you could also do this:
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
NSColor *textColor = (backgroundStyle == NSBackgroundStyleDark) ? [NSColor windowBackgroundColor] : [NSColor controlShadowColor];
self.detailTextField.textColor = textColor;
[super setBackgroundStyle:backgroundStyle];
}
See also here: http://gentlebytes.com/blog/2011/08/30/view-based-table-views-in-lion-part-1-of-2/
Just subclass NSTableCellView then implement drawRect:
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
if (self.backgroundStyle == NSBackgroundStyleDark) {
[yourTextFieldIVar setTextColor:[NSColor whiteColor]];
} else if(self.backgroundStyle == NSBackgroundStyleLight) {
[yourTextFieldIVar setTextColor:[NSColor blackColor]];
}
}
I have a view based NSTableView with a custom NSTableCellView. This custom NSTableCellView has several labels (NSTextField). The whole UI of the NSTableCellView is built in IB.
The NSTableCellView can be in a normal state and in a selected state. In the normal state all text labels should be black, in the selected state they should be white.
How can I manage this?
Override setBackgroundStyle: on the NSTableCellView to know when the background changes which is what affects what text color you should use in your cell.
For instance:
- (void)setBackgroundStyle:(NSBackgroundStyle)style
{
[super setBackgroundStyle:style];
// If the cell's text color is black, this sets it to white
[((NSCell *)self.descriptionField.cell) setBackgroundStyle:style];
// Otherwise you need to change the color manually
switch (style) {
case NSBackgroundStyleLight:
[self.descriptionField setTextColor:[NSColor colorWithCalibratedWhite:0.4 alpha:1.0]];
break;
case NSBackgroundStyleDark:
default:
[self.descriptionField setTextColor:[NSColor colorWithCalibratedWhite:1.0 alpha:1.0]];
break;
}
}
In source list table views the cell view's background style is set to Light, as is its textField's backgroundStyle, however the textField also draws a shadow under its text and haven't yet found exactly what is controlling that / determining that should it happen.
Probably the easiest way to accomplish this would be to subclass NSTextField and to override the drawRect: method in your subclass. There you can determine whether the NSTableCellView instance containing your NSTextField instances is currently selected by using this code (which I use with a NSOutlineView, but it should also work with NSTableView):
BOOL selected = NO;
id tableView = [[[self superview] superview] superview];
if ([tableView isKindOfClass:[NSTableView class]]) {
NSInteger row = [tableView selectedRow];
if (row != -1) {
id cellView = [tableView viewAtColumn:0 row:row makeIfNecessary:YES];
if ([cellView isEqualTo:[self superview]]) selected = YES;
}
}
Then draw the view like this:
if (selected) {
// set your color here
// draw [self stringValue] here in [self bounds]
} else {
// call [super drawRect]
}
This works no matter what style the table view has:
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
[super setBackgroundStyle:backgroundStyle];
NSTableView *tableView = self.enclosingScrollView.documentView;
BOOL tableViewIsFirstResponder = [tableView isEqual:[self.window firstResponder]];
NSColor *color = nil;
if(backgroundStyle == NSBackgroundStyleLight) {
color = tableViewIsFirstResponder ? [NSColor lightGrayColor] : [NSColor darkGrayColor];
} else {
color = [NSColor whiteColor];
}
myTextField.textColor = color;
}
Swift 4
override var backgroundStyle: NSView.BackgroundStyle {
get {
return super.backgroundStyle
}
set {
self.yourCustomLabel.textColor = NSColor(calibratedWhite: 0.0, alpha: 1.0)//black
}
}
I'm using an NSTextField and customizing the fieldEditor using the setupFieldEditorAttributes: method. This allows me to set custom foreground and background colors for the selected text, which is important because my textField has a black background and white text. Generally, this works fine. However, my settings seem to be overridden when I deactivate the application and the window is no longer key. The fieldEditor NSTextView remains there, but drawing changes to a white text color and light gray selection color (the defaults). Does anyone have suggestions for how I can customize this drawing?
You can override [NSWindow willReturnFieldEditor:toObject:] and return there custom NSTextView with changed selection color.
Inspired by the answer to this question, the solution is to create an override of the NSLayoutManager that customizes the way in which the highlighting is performed based on the first responder state of the NSText view that owns it.
If the text view associated with this custom layout manager is the first responder, then it draws the selection using the color provided by macOS. If the text view is not the first responder, it uses the text view's background color as the selection color unless a custom color is provided via the setCustomInactiveColor method.
// ---------------------------------------------------------------------------
// IZLayoutManager CLASS
// ---------------------------------------------------------------------------
// Override NSLayoutManager to change how the currently selected text is
// highlighted when the owning NSTextView is not the first responder.
#interface IZLayoutManager : NSLayoutManager
{
}
-(instancetype)initWithOwningTextView:(NSTextView*)inOwningTextView;
#property (nullable, assign, nonatomic) NSTextView* owningTextView;
#property (nullable, strong, nonatomic) NSColor* customInactiveColor;
#end
#implementation IZLayoutManager
- (instancetype)initWithOwningTextView:(NSTextView*)inOwningTextView
{
self = [super init];
if (self) {
self.owningTextView = inOwningTextView;
}
return self;
}
- (void) dealloc
{
// my project is non-ARC; so we maually release any custom color
// we received; in non-ARC projects this is probably not necessary
if (self.customInactiveColor != NULL) {
[self.customInactiveColor release];
self.customInactiveColor = NULL;
}
[super dealloc];
}
// see extensive description of fillBackgroundRectArray in NSLayoutManager.h
// TL;DR: if you change the background color here, you must restore it before
// returning from this call
- (void) fillBackgroundRectArray:(const NSRect *)rectArray count:(NSUInteger)rectCount forCharacterRange:(NSRange)charRange color:(NSColor *)color
{
BOOL needToReestoreColor = NO;
if (self.owningTextView != NULL && [[self.owningTextView window] firstResponder] != self.owningTextView) {
if (self.customInactiveColor != NULL) {
[self.customInactiveColor setFill];
} else {
[[self.owningTextView backgroundColor] setFill];
}
needToReestoreColor = true;
}
[super fillBackgroundRectArray:rectArray count:rectCount forCharacterRange:charRange color:color];
if (needToReestoreColor) {
[color setFill];
}
}
#end
Then, after you've allocated the NSTextView, you need to do this:
NSTextView* myTextView = ... // get a reference to your text view
// allocate our custom layout manager
IZLayoutManager* layoutManager = [[[IZLayoutManager alloc] initWithOwningTextView:self] autorelease];
// if you want to use a color other than the background for
// the selected text, uncomment the following line and
// supply your desired color
// [layoutManager setCustomInactiveColor:[NSColor redColor]];
[[myTextView textContainer] replaceLayoutManager:layoutManager];