setString vs. setStringValue in Cocoa - cocoa

I tried to use the method setString to change the text in a textfield and it didn't work. Then I changed it to setStringValue and it worked. So what is setString used for?

NSTextField does not have a setString: method. NSTextField is a type of NSControl, and NSControl has a setStringValue: method.
NSText and its more famous subclass NSTextView have a setString: method. #John Boker is correct that the field editor is an NSText, but you still can't send setString: to an NSTextField, even in edit mode. You'd need to get the field editor from the window and then call setString: on that (not that you really should do that).
While it is confusing to newcomers, there is a good rationale behind the different methods. NSControl has a "value", and that value can take different types (setDoubleValue:, setObjectValue:, etc.) This allows me to set the value as a double, but then retrieve in as a string. For the broad range of possible controls, this makes good sense and is very flexible. NSText is not a control; note how it doesn't have setAction: or setTarget: either. Its purpose is to display and edit (attributed) strings. When you call string, you are actually getting the real backing text storage, not just the value of the object in string form (as stringValue does for NSControl).
That said, when you say "it didn't work," I hope you mean that you got a compiler warning about this that told you that NSTextField may not respond to setString:. If you're ignoring compiler warnings, then you're going to have a lot of problems in ObjC.

from: http://macosx.com/forums/software-programming-web-scripting/43905-_outlet-setstring-testing-selector-not-recognized.html#5
setString: is for NSText, which is the
class used for an NSTextField's field
editor while it is being edited, and
for the NSText subclass NSTextView
which can display and edit styled text
as in TextEdit.
So the way i take it is you can only use setString: when the field is in an edit mode, i think.

Going a bit more generically (in contrast to my comment on Rob's answer):
Objects respond to messages. You can send a message to any object that responds to that message; you can't send a message to any object that doesn't respond to it.
Instances of NSText and its subclass NSTextView respond to setString:, so you can send NSText objects and NSTextView objects setString: messages.
Instances of NSControl and its subclasses, including NSTextField, respond to setStringValue:, so you can send NSControl objects, NSTextField objects, and other control objects setStringValue: messages.
See Rob's answer and my comment on it for why these two specific branches of the class hierarchy have these seemingly-similar-but-differently-named methods.

Related

how to detect double Click in NSTextField

I have a custom NSTextField and I'd like to detect double clicks by the user in the text field. My goal: I want to be able to double click on a parenthesis in an expression, such as "(2+2) = 4" and have it select everything inside the matching parentheses. Thought I could do this with...
- (void)textView:(NSTextView *)textView doubleClickedOnCell:(id <NSTextAttachmentCell>)cell inRect:(NSRect)cellFrame atIndex:(NSUInteger)charIndex;
but it never gets called in my custom NSTextField.
Then I thought I could override -mouseDown, but that isn't getting called either. I'm stumped. Any suggestions for what should be an easy function to implement.
Thanks!
Philip
A text field does not handling editing, as such. When a text field has focus, a text view is added to the window, overlapping the area of the text field. This is called the "field editor" and it is responsible for handling editing.
It seems the most likely place for you to change the behavior of a double-click is in the text storage object used by that text view. NSTextStorage inherits from NSMutableAttributedString which inherits from NSAttributedString which has a -doubleClickAtIndex: method. That method returns the range of the text that should be selected by a double-click at a particular index.
So, you'll want to implement a subclass of NSTextStorage that overrides that method and returns a different result in some circumstances. NSTextStorage is a semi-abstract base class of a class cluster. Subclassing it requires a bit more than usual. You have to implement the primitive methods of NSAttributedString and NSMutableAttributedString. See the docs about it.
There are a few places to customize the field editor by replacing its text storage object with an instance of your class:
You could implement a custom subclass of NSTextFieldCell. Set your text field to use this as its cell. In your subclass, override -fieldEditorForView:. In your override, instantiate an NSTextView. Obtain its layoutManager and call -replaceTextStorage: on that, passing it an instance of your custom text storage class. (This is easier than putting together the hierarchy of objects that is involved with text editing, although you could do that yourself.) Set the fieldEditor property of the text view to true and return it.
In your window delegate, implement -windowWillReturnFieldEditor:toObject:. Create, configure, and return an NSTextView using your custom text storage, as above.
it is simple just use this class to detect double tap
final class doubleClickableTextField : NSTextField {
override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event)
if (event.clickCount == 2){
// do the work here
self.isEditable = true
}
}
}
The answer from Ken Thomases here is correct in its analysis of the issue regarding the field editor and how to replace it, but the solution it then recommends – replacing the NSTextStorage of the field editor – is not the correct solution, according to Apple. In their doc they specifically recommend that for delimiter-balancing the selectionRangeForProposedRange:granularity: method should be used. Once you have a custom field editor going, as per Ken's answer, you should therefore use the solution for NSTextView here, applied to a custom NSTextView subclass that you use for your field editor.
In case it is of interest, using NSTextStorage's doubleClickAtIndex: method for delimiter-balancing is probably the wrong solution for several trivial reasons: (1) because Apple says so, (2) because subclassing NSTextStorage is complicated and error-prone, and (3) because NSTextView provides a method specifically intended for the purpose of doing things like delimiter-balancing. But it is also wrong for a non-trivial reason: (4) that doubleClickAtIndex: is documented as "Returns the range of characters that form a word (or other linguistic unit) surrounding the given index, taking language characteristics into account". So doubleClickAtIndex: is really about how the linguistic units of the text (i.e. words) are defined, and redefining those in some way to make delimiter-balancing work would probably break other aspects of word-level text processing. For example, I would guess that it would be pretty tricky to make double-click-drag (dragging out a selection word by word) work properly if you have overridden doubleClickAtIndex: to do delimiter balancing. Cocoa may use doubleClickAtIndex: for other aspects of word-finding too, and may add more uses of it in the future. Since a delimiter-balanced section of text is not a "word", who knows what weirdness might result.

Proper way to replace NSTextStorage in NSTextView?

I am making some text viewer app. Currently I need very frequent and precise line handling ability, so I want to subclass NSTextStorage class. But I couldn't find any method to set a new text storage to NSTextView. The only method I could find was
-[NSLayoutManager replaceTextStorage:]
method. But it's confusing whether this is what I was looking for. Because it seems just replace text storage of linked NSLayoutManagers instead of NSTextView.
I also considered subclassing NSTextView and overriding -textStorage method, but if the class is not designed for subclassing, it will make undefined result.
Anyone has tried to use custom NSTextStorage on NSTextView? How can I do this? Or is this prohibited by design?
You can do something like this to change the storage for an NSTextView:
NSTextStorage *newStorage = [[NSTextStorage alloc] initWithString: #"test"];
[aTextView.layoutManager replaceTextStorage: newStorage];
Since NSTextStorage is a subclass of NSMutableAttributedString you can manipulate it with all of the same methods.

How to tell which NSTextField was edited?

My NSDocument subclass adopts NSTextFieldDelegate. The document window contains several NSTextField instances (all setup as outlets of the document class). When the user edits a text field, I want my document to be notified. But all the methods in the NSTextFieldDelegate protocol are inherited from NSTextViewDelegate and hence pass NSText* instances in their parameters, NOT NSTextField instances. The same applies to the notification:
- (void) controlTextDidChange:(NSNotification*) notification
So, How do I find out which of the many NSTextField instances is being edited?
NOTE
I need to register undos properly, using the document's undo manager. I tried implementing
- (NSUndoManager *)undoManagerForTextView:(NSTextView *)aTextView
but this seems to only work with NSTextViews, NOT NSTextFields.
You can get a reference to your NSTextField as [notification object]. From the documentation for NSControlTextDidChangeNotification:
The notification object is the NSControl object posting the
notification.
The actual control subclass will be your NSTextField.

Making selected text bold in an NSTextView

I'm using the following code to make selected text bold in an NSTextView
[self.textView.attributedString addAttribute:NSFontAttributeName value:[NSFont boldSystemFontOfSize:12] range:self.textView.selectedRange];
Where self.textView is an outlet to an NSTextView . Xcode gives a warning that addAttribute may not work as the property is of type NSAttributedString and not NSMutableAttributedString.The code works but is it wrong to do it this way ? If so what is the proper way ?
UPDATE :
I found another way of doing this :
NSMutableAttributedString *textFieldText = [self.textView.attributedString mutableCopy];
[textFieldText addAttribute:NSFontAttributeName value:[NSFont boldSystemFontOfSize:12] range:self.textView.selectedRange];
[self.textView.textStorage setAttributedString:textFieldText];
Since both methods work , I'd like to know which is better.
General Remarks
Take the interface that a class exposes at its word. It is not best practice to assume that the return type is a specific subclass of the declared return type.
This is especially important in the context of a class cluster: different implementations of the common interface may have different different return types for the same method though those return types are guaranteed to be compatible with the type declared in the header.
Supposing you could be guaranteed that all undocumented subclasses in the class cluster presently return the same specific subclass of the type declared in the header, you cannot be guaranteed that that will remain the case in future revisions of Apple's frameworks.
NSTextView
The question is about an NSTextView's and its property textStorage. This property is of type NSTextStorage, a "semiconcrete subclass of NSMutableAttributedString". The documentation goes on, describing the preferred mechanism for changing the string stored by the NSTextStorage or its attributes:
use the text access methods defined by NSMutableAttributedString, NSAttributedString, NSMutableString, and NSString to perform character-level manipulation.
NSTextView exposes its property textStorage which is an instance of NSTextStorage. NSTextStorage is a subclass of NSMutableAttributedString. Consequently, we can simply add our attribute to it:
[self.textView.textStorage addAttribute:NSFontAttributeName
value:[NSFont boldSystemFontOfSize:12.0f]
range:self.textView.selectedRange];

Binding an NSColorWell to an NSTextField

I'm attempting to bind an NSColorWell to the NSTextField value and implementing an NSFormatter subclass. The NSColorWell is bound to an ivar in the app delegate which is also bound to the NSTextField. There is an NSFormatter that converts the [NSColorWell color] to a descriptive string.
When the UI focus is on the NSTextField, it doesn't update at all; but when the focus is on another NSTextField, it does.
I understand KVO/KVC, bindings etc. but wonder if there are any 'gotchas' in IB that I'm missing.
Try a bidirectional value transformer instead. For one advantage, you may find it easier to implement than a formatter.

Resources