NSTextField : How to draw background only when focused - cocoa

I put a textfield in a window, and I want the textfield draw background only when focused.
I know that all the controls in the window share one field editor.
I tried subclass nstextfield and implement becomeFirstResponder and resignFirstResponder.
And tried use custom singleton editor for the window .
Any one know how to achieve this?
In the NSWindow ,every textfield or button share one instance of field editor(a singleton NSTextView instance),so when you click the textfield, textfield become firstResponser first,and then quickly pass it to the shared field editor. So when the textfield lost focus ,the resignFirstResponder of the textfield will never be called(because the field editor is the FirstResponder now).
You can look at fieldEditor:forObject: in NSWindow API.
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSWindow_Class/Reference/Reference.html#//apple_ref/occ/instm/NSWindow/fieldEditor:forObject:
SOLUTION:
(Thanks , Michael Gorbach)
In my window controller
- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)anObject
{
NSText *text = [sender fieldEditor:YES forObject:self];
if(text&&[anObject isKindOfClass:[MyCustomTextField class]])
{
[text setBackgroundColor:[NSColor whiteColor]];
[text setDrawsBackground:YES];
}
return text;
}

I just did this recently, in a tableView. You need to use a custom cell and fieldEditor. Specifically, you need to call setDrawsbackground:YES on the NSText/NSTextView object that is the field editor, and setBackground: to configure your color of choice. There are two places to set up a custom field editor.
One is to implement setUpFieldEditorAttributes: on a custom NSTextFieldCell subclass that you have configured your NSTextField to use, and another is to use the window or window delegate method windowWillReturnFieldEditor:toObject:.
Note that if the first method doesn't work for a particular setting, sometimes you need to use the second, because it gets in earlier in the codepath.

Related

selectable NSTextField and NSColorPanel – how to break their undesired interplay?

In a seemingly trivial setup, I encounter an undesired interplay between a selectable NSTextField and an NSColorPanel that I cannot get rid of and that drives me nuts.
Here’s the setup: Within one window, I have a selectable Multi-Line Label (de facto an NSTextField) and an NSColorWell.
The Color Well allows the user to color geometric objects in the GUI; it has nothing to do with text whatsoever. Of course, clicking on the color well activates it, i.e. brings up the shared NSColorPanel and connects the color well to it.
The Text Field is completely independent from the colored objects in the GUI and presents data to the user. It is read-only, i.e. not editable. Since the data is organized in columns, I use tabs for text formatting and the setAttributedStringValue: method of NSTextField to display the data.
At first glimpse, everything works as you would expect in a such a trivial setup.
But here comes the rub: I want the user to be able to copy the data in the text field to process it elsewhere. Therefore, the NSTextField has to be selectable. And setting it to be selectable is where the problems start:
When the user clicks on the selectable text field to select the text, the window’s field editor takes over, and as a consequence, all the tab settings of the attributed text are lost and the text gets mingled. The usual way to prevent this is to set the allowsEditingTextAttributes property of the NSTextField to YES. If I do this, the tab formatting is preserved when the user selects the text. But now the NSColorPanel (if visible) unintentionally also switches to the text color (always black), and if the color well is active (connected to the NSColorPanel), it will remain active, thereby changing the color of all geometric GUI objects to black. Ouch!
I have found no way to set the selectable and allowsEditingTextAttributes properties of NSTextField to YES but still prevent it from communicating with the NSColorPanel.
The obvious alternative route would be to preserve the tab formatting for selected text even with allowsEditingTextAttributes set to NO (which would disconnect the color panel from the text field, as desired). But I’ve had no success with this approach either, although I do not really understand why:
My idea was to set the required tabs as the defaultParagraphStyle of the field editor of the text field. So, I set up a customized field editor:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSArray *myTabs = #[
[[NSTextTab alloc] initWithType:NSRightTabStopType location:100],
[[NSTextTab alloc] initWithType:NSRightTabStopType location:200],
[[NSTextTab alloc] initWithType:NSRightTabStopType location:300]
];
NSMutableParagraphStyle *myParagraphStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
[myParagraphStyle setTabStops:myTabs];
myFieldEditor = [NSTextView new]; // myFieldEditor is an instance variable
[myFieldEditor setDefaultParagraphStyle:myParagraphStyle];
[window setDelegate:self];
[window fieldEditor:YES forObject:myTextField];
}
And activate it for the text field in the windowWillReturnFieldEditor:toObject: delegate method:
- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client
{
if (client == myTextField) return myFieldEditor;
return nil;
}
I even made sure that my custom field editor is indeed used by subclassing the NSTextFieldCell of my text field and logging the propagated field editor:
#implementation myTextFieldCell
- (NSText *)setUpFieldEditorAttributes:(NSText *)textObj
{
NSTextView *newTextObj = (NSTextView*)[super setUpFieldEditorAttributes:textObj];
NSLog(#"STYLE: %#", [newTextObj defaultParagraphStyle]);
return newTextObj;
}
#end
Now, when I select the text in the text field, I get the following log output:
2017-11-02 11:51:07.432 Demo[94807:303] STYLE: Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
100R,
200R,
300R
), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningFactor 0.05, HeaderLevel 0
Which is exactly what is expected.
But still, the tab formatting disappears in the text field as soon as I select the text. I have no idea why this does not work.
So I’m stuck either way. If I set the allowsEditingTextAttributes property of NSTextField to YES, tab formatting is preserved when the text is selected, but my colored objects in the GUI unintentionally change to black. If I set the allowsEditingTextAttributes property to NO, the color panel behaves as it should, but the tab formatting is lost as soon as I select the text.
This is a very unfortunate case of Cocoa trying to be too smart and thereby making a completely trivial setup a huge issue.
Any ideas anyone?
OK, so I ended up with the suggestion that #Willeke (thanks!!) made in his comment to my question: To use NSTextView instead of NSTextField to implement my Multi-Line Label.
I will first sum up why it seems impossible to do what I wanted with NSTextField, and then the solution with NSTextView.
Why NSTextField doesn’t work
As described, my idea for a solution was to customize the field editor for the NSTextField, setting the tab stops I need, so that I need not set NSTextField’s allowsEditingTextAttributes property to YES (which would unintentionally couple the text field to the color panel). This, I hoped, would preserve the tab stops of my attributed string’s paragraph style when I select the text in the text field and thereby activate the field editor.
Extensive testing has shown that this does not work for several reasons:
As #Willeke pointed out, setting the usesFontPanel property of NSTextView to NO also breaks the connection of the text view to the color panel (as desired). However, this does not work for the NSTextView that is the field editor of the NSTextField, because in this context, this setting is always overwritten by the allowsEditingTextAttributes property of the NSTextField: If allowsEditingTextAttributes is YES, the font and color panels are coupled regardless of the value of usesFontPanel, if it is NO, the font and color panels are decoupled regardless of the value of usesFontPanel.
The idea to use the tab stops of a customized field editor instead of using the tab stops of my attributed string’s paragraph style (which would require allowsEditingTextAttributes to be YES) wouldn’t work, anyway, because the tab stops settings of the field editor are obviously always completely ignored by NSTextField, regardless of the value of the allowsEditingTextAttributes property. NSTextField always uses the evenly spaced default tab stops.
Judging from intense googling, the other variant – setting allowsEditingTextAttributes to YES but somehow modifying NSColorPanel to not connect to the NSTextField nevertheless – is impossible to implement without recurring to private methods of NSColorPanel.
How to implement the solution with NSTextView
While it seems overkill to instantiate a complete NSTextView embedded in a clip view and a scroll view just to get the functionality of a text field, in the end it’s the easiest (or even only possible) solution.
To make the scroll view disappear, you’ll have to basically uncheck everything in the NSScrollView’s Attribute inspector in IB, in particular Show Vertical Scroller. Set Draw Background and the border type to the kind of appearance you want; if you want to mimic a multi-line label (like I did), uncheck Draw Background and choose the invisible border type. In the Attribute inspector of the embedded NSTextView, also uncheck all attributes except Selectable, in particular Uses Font Panel.
Make sure the size of the NSTextView is large enough to hold the complete content string to avoid unintentional scrolling effects and fix the text position. If your content string ends with a line brake, you’ll need enough space for an empty line beneath it. If you did not uncheck Draw Background and this does not look the way you want, don’t draw the background of either the NSScrollView or the NSTextView, select the invisible border for NSScrollView and then put an NSBox of the desired size and appearance beneath them.
You can now set the attributed content string with:
[[myTextView textStorage] setAttributedString:myAttributedString];
Note that this does work although the editable property of NSTextView is set to NO since you’re modifying the NSTextStorage, not the NSTextView itself.
But unfortunately, we’re not finished yet.
When you’re using an NSTextField to display readonly data as you typically do in a Label, more often than not you would not want the text field to be part of your key view loop (that circles through your controls by pressing the Tab key). To achieve this, you can simply set the refusesFirstResponder property of NSTextField to YES. But NSTextView does not inherit from NSControl and therefore does not have this property. So in the end, we’ll have to subclass NSTextView to add the refusesFirstResponder property.
The implementation overwrites becomeFirstResponder and goes like this:
- (BOOL)becomeFirstResponder
{
if (!_refusesFirstResponder) return [super becomeFirstResponder];
NSEvent *event = [NSApp currentEvent];
if ([event type] == NSLeftMouseDown || [event type] == NSRightMouseDown) return [super becomeFirstResponder];
NSView *validKeyView = ([event modifierFlags] & NSShiftKeyMask)? [[[self previousValidKeyView] previousValidKeyView] previousValidKeyView] : [self nextValidKeyView];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{[[self window] makeFirstResponder:validKeyView];}];
return NO;
}
If refusesFirstResponder is NO, we simply return super’s implementation.
If it is YES, we check if the NSTextView is about to become first responder because of a mouse click within it. If so, we also simply return super’s implementation, thereby allowing text selection with the mouse.
Other than that, we forward the first responder request to the next or previous key view (depending on whether the Shift key was pressed) and return NO, refusing to become first responder. Determining the previous key view is a bit tricky because the closest previous key view is the embedding NSClipView we don’t want or need, but have to use because Interface Builder does not offer a “pure” NSTextView. Then comes the embedding NSScrollView, and only then the previous key view we actually want.
Also, since we’re amidst a process which determines the first responder, we cannot simply invoke makeFirstResponder:, but have to postpone it to the next iteration of the run loop.
Now that we have implemented refusesFirstResponder, we’ll still have to mimic NSTextField’s behavior to dismiss any text selection when it loses first responder state. We can do this in an NSText delegate method. Assuming we don’t need other delegate functionality, we can make our subclass its own delegate and add this delegate method:
- (void)textDidEndEditing:(NSNotification*)notification
{
[[notification object] setSelectedRange:NSMakeRange(UINT64_MAX, 0)];
}
Finally, if we have to subclass, anyway, we might as well add a setAttributedString: convenience method.
So what we’ll end up with is this:
Header:
#import <Cocoa/Cocoa.h>
IB_DESIGNABLE
#interface MyTextFieldLikeTextView : NSTextView <NSTextViewDelegate>
#property IBInspectable BOOL refusesFirstResponder;
- (void)setAttributedString:(NSAttributedString*)attributedString;
#end
Implementation:
#import "MyTextFieldLikeTextView.h"
#implementation MyTextFieldLikeTextView
- (void)awakeFromNib
{
[self setDelegate:self];
}
- (BOOL)becomeFirstResponder
{
if (!_refusesFirstResponder) return [super becomeFirstResponder];
NSEvent *event = [NSApp currentEvent];
if ([event type] == NSLeftMouseDown || [event type] == NSRightMouseDown) return [super becomeFirstResponder];
NSView *validKeyView = ([event modifierFlags] & NSShiftKeyMask)? [[[self previousValidKeyView] previousValidKeyView] previousValidKeyView] : [self nextValidKeyView];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{[[self window] makeFirstResponder:validKeyView];}];
return NO;
}
- (void)textDidEndEditing:(NSNotification*)notification
{
[[notification object] setSelectedRange:NSMakeRange(UINT64_MAX, 0)];
}
- (void)setAttributedString:(NSAttributedString*)attributedString
{
[[self textStorage] setAttributedString:attributedString];
}
#end
Still a lot of effort only because Cocoa tries to outsmart us and insists on connecting the NSColorPanel to each and every NSTextField that allows for attributed text …

NSTextView, where is it coming from when using NSSearchField and NSTextField

Where does NSTextView come from?
In my OSX application i have a NSSearchField, i implemented controlTextDidChange: which contains in key NSFieldEditor a NSTextView instance
- (void)controlTextDidChange:(NSNotification *)note {
NSTextView * searchField = note.userInfo[#"NSFieldEditor"];
...
}
also when asking
id firstResponder = [self.window firstResponder];
i get a NSTextView
But where is it coming from??? as NSSearchField nor NSTextField is NOT derived from NSTextView... also not it's cell... also i cannot find a property for it in NSSearchField
I'am using NSSearchField for sure
also when connecting the action outlet to NSSearchField, the sender is NSSearchField (which is ok)
- (IBAction)searchFieldCommit:(NSSearchField *)sender {
//...
}
What is this mess, why do i get NSSearchField vs NSTextView and how to access the NSSearchField from NSTextView and vice versa
Cocoa maintains a shared NSTextView called the "field editor". It is described in more detail here:
https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TextFieldsAndViews/TextFieldsAndViews.html
Aderstedt is right about the field editor. The other thing you need to know is that in your controlTextDidChange: method, note.object will be the control that sent the notification.

how do i stop NSTextField blocking right clicks for NSTableView rows?

As the title suggest, I've enabled right clicks for my tableview with customized rows. Anywhere there is an NSTextField, it blocks the right click.
is there a userInteractionEnabled equivalent for cocoa like on the iphone?
I though I probably needed to subclass everything in my NSTableCellView subclass, but I just needed to override -(NSView*)hitTest:(NSPoint)aPoint method in my NSTableCellView subclass to return self.
I'm able to right click on text fields within my custom view based table view cell. Here is how I configure it:
NSTextField *tf = [[NSTextField alloc] initWithFrame:NSZeroRect];
self.textField = tf;
self.imageView.autoresizingMask=NSViewWidthSizable;
self.textField.editable=NO;
self.textField.selectable=NO;
self.textField.drawsBackground=NO;
self.textField.bordered=NO;
self.textField.bezeled=NO;
self.textField.target=self;
self.textField.action=#selector(textDidEndEditing:);
[self.textField.cell setLineBreakMode:NSLineBreakByTruncatingMiddle];
Also, make sure you are setting the -menu property of NSTableView and not the cell view to enable to menu. (I don't know if that will make a difference to your issue but it is how I do right clicking in a table view.)

Adding an editable NSTextFieldCell to my NSTableView

I have an NSTableView which displays some information representing a custom object of mine. I am not using bindings.
Typically, I create my own NSCells to display data, but for once I'm after an NSTextFieldCell that will display a string value of the object, as well as let the user edit it.
I can successfully add the NSTextFieldCell using the code below, but it is not editable.
NSTextFieldCell *textField = [[NSTextFieldCell alloc] init];
[textField setFont:[NSFont fontWithName:#"Helvetica Bold" size:13]];
[textField setTextColor:[NSColor colorWithDeviceRed:0.1058823529 green:0.2117647059 blue:0.3294117647 alpha:1.0]];
[textField setStringValue:projectName];
[textField setEditable:YES];
[textField setBordered:NO];
[textField drawWithFrame:textRect inView:controlView];
[textField release];
Could someone please help me with this?
NSTextFieldCell is implemented with the flyweight pattern (I read about it in the "Cocoa Design Patterns" book) and each column has only one instance of a cell. You can see some kind of evidence of this when you edit it in interface builder. When you click to edit an NSTableView, that single instance of the cell jumps in from where it was before and handles the editing for you.
As you say, doing this works for the visual appearance (drawing) of the cell, and works for NSTextField as well because each NSTextField must have just one cell per view, and therefore it's around when you want to edit it.
However in this case, you are creating a cell, drawing it, then kicking it out of memory by releasing it at the end of your code. So how do you expect this cell which you have set as editable to be around when you try to edit it? It doesn't exist anymore.
Try creating a single cell when the table view is set up and setting your custom cell to the
right table column using this:
- (void)setDataCell:(NSCell *)aCell
Alternatively you could subclass NSTextFieldCell and do your customization there, and set the cell class for the column in interface builder (or XCode 4 if you're on the bleeding edge!)

NSOutlineView with transparent field editor

I'm working with a NSOutlineView located on a HUD panel. I configured it so that it doesn't draw its background. Everything looks fine until I double click to edit a cell.
The field editor draws its background and focus ring which completely ruin the whole user experience.
This is what I'm doing in the subclass of NSTextFieldCell:
- (NSText *)setUpFieldEditorAttributes:(NSText *)textObj
{
NSText *text = [super setUpFieldEditorAttributes:textObj];
[text setDrawsBackground:YES];
[text setBackgroundColor:[NSColor darkGrayColor]];
return text;
}
If I use setDrawsBackground:NO it's completely ignored and I get a white background. My solution is far from being good because I can't touch the alpha component of the color (if I do that, again the field editor will use another color as a background), but at least I don't get a white background.
I'm wondering if there's an actual solution to this problem. Do I have to provide my own field editor? Is it worth it?
What I want is simply a field editor with no background and no focus ring, just the cursor blinking.
Thanks!
The problem is that the white background is drawn by NSTableView when it's sent -editColumn:row:withEvent:select:. It fills the cell's rect with +[NSColor textBackgroundColor].
If there's a public API for overriding the current setting for named colors from the developer colorspace, we could set it inside an override of -editColumn:row:withEvent:select: or the like. I do not recall such an API (pointers are appreciated). ALSO: I've only tested this code on Snow Leopard (even the Leopard SDK addendum below). Verify the code against the actual SDKs and runtime environments you intend to support.
NSTableView has a private accessor it uses for the fill color, but it's a read-only property. No setter, so we can't just change the value on a standard NSTableView. We must subclass it. (Since you want the same behavior in an outlineView and NSOutlineView is already a subclass of NSTableView, we're going to subclass NSOutlineView. But, aside from the superclass, the code is identical.)
#interface ASCOutlineView : NSOutlineView {
}
#end
#implementation ASCOutlineView
- _textBackgroundColor
{
return ([NSColor clearColor]);
}
#end
seems to be all one needs to prevent that glaring white block from ruining your HUD when editing table cells in Snow Leopard.
Apps compiled against the Leopard SDK need a little more support though. Leopard's tableViews may have hard-coded some rendering properties so we need to override a choice method.
NSTextFieldCells are actually wrappers for NSTextViews so they can be used inside controls. They normally share the same textView instance, which is managed by the window (or its subclass, panel, in this case). NSTableView alters the settings of the NSTextFieldCell to conform to system UI settings for editing data. Mostly. The NSTextFieldCell then propagates those settings to the NSTextView. At any point along this pipeline we can override a method or two to alter the values of those properties to match our own UI.
I use -[NSTextFieldCell setDrawsBackground:] because it requires little effort to get correct. It's also important to keep the internal state as consistent with the effect we're hoping to achieve in the event some other object might depend on that state.
#interface ASCTextFieldCell : NSTextFieldCell {
}
#end
#implementation ASCTextFieldCell
- (void)setDrawsBackground: (BOOL)flag
{
[super setDrawsBackground: NO];
}
#end
And preventing the focus ring from appearing while the cell's being edited is a simple matter of changing the setting of its focus ring type. Frustratingly, IB doesn't provide access to this property, so it must be done programmatically:
for(eachColumn in [hudOutlineView tableColumns])
{
columnCell = [[ASCTextFieldCell alloc] initTextCell: #""];
[eachColumn setDataCell: columnCell];
if([columnCell respondsToSelector: #selector(setFocusRingType:)] != NO)
[(NSTextFieldCell *)columnCell setFocusRingType: NSFocusRingTypeNone];
}
It looks like there is other background behind field editor, which is drawn as white.
Probably, NSCell, or background of row, whatever else.

Resources