Making selected text bold in an NSTextView - cocoa

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];

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 access an object's NSDocument?

I can access an app-wide delegate instance using [NSApp delegate] after adding an NSObject to the mainmenu.xib, setting the name of the object to the name of my appDelegate and setting the mainmenu.xib delegate to this object.
Now, what I would like to do, is to access to an object's Document, i.e. the active NSDocument that the object "belongs" to. It would be a doc-wide delegate instance I guess. Sometimes [self document] works, but not always. Is there a generic way?
There is no need to pass a reference explicitly. You may access the document from NSViewController in the following way:
id document = self.view.window.windowController.document;
What about [[NSDocumentController sharedDocumentController] currentDocument] ?
Be careful nevertheless.
Please read
NSDocumentController currentDocument returning nil
For any sub windows that are part of the document, it turns out that it's very easy to make a very simple subclass of NSViewController and store the required information in there. These view controllers are set up within the main Document implementation so it is easy to pass the address of the NSDocument object. Any actual subview can then be controlled by a view controller that is a subclass of this "managing controller".
This solution does not work for every object, but it does take the biggest hurdle and solves my problem...

Manual binding in Cocoa

I have an ImageView which shows a lock, informing if an opened file is locked or not. I have 2 images for locked and unlocked cases. I want synchronize the displayed image with boolean value of my object representing an opened file.
To do this I want my ViewController to change the image in my ImageView depending on lock state of object. So both object and ViewController have a property "isLocked".
How can I synchronize them? It is easy in IB but I don't know how to do it programmatically. I tried in initialize method of my ViewController to use:
[ViewController bind:#"value" toObject:[ArrayController selection] withKeyPath:#"isLocked" options:nil];
But it doesn't work. In documentation it is said that I have to expose my binding before using it.
I try to put the following code in initializer method of my object:
[self exposeBinding:#"isLocked"];
But Xcode doesn't recognize this method.
Does somebody have experience with this kind of bindings establishing?
As #nick says, you want Key-Value-Observing.
[arrayController addObserver:self
forKeyPath:#"selection.isLocked"
options:NSKeyValueObservingOptionNew
context:#"this_context"]
Then when isLocked changes the -observeValueForKeyPath:ofObject:change:context: method that you have added to your viewController will be called (as long as you only manipulate isLocked in a KVC compliant way).
The options parameter lets you optionally tweak exactly what conditions will trigger the notification and what data is sent along with the notification. The context parameter is there to help you distinguish between notifications that you registered to receive and notifications your superclass registered to receive. It is optional.
Bindings seem like they might be useful to keep two values in sync. However, this is not what they do at all.
Yes, lots of things seem to give the impression that this is what they do, and there isn't much saying that this isn't what they do, also lots of people believe that this is what they do - but no, you cannot use them for this.
Only a handful of classes support bindings (they are listed here) and then, and this is the important bit, those classes only support binding their named bindings, and these bindings are not instance variables. eg NSTextField has a 'fontFamilyName' binding yet NSTextField does not have a 'fontFamilyName' property or instance variable, even a derived one. NSTextField does have a 'isBordered' property but not a binding - so you cannot bind 'isBordered'.
It does not mean anything to 'bind' an arbitrary property of an arbitrary Class.
Yes, you can bind two arbitrary values, the following code works just fine:
#import <Cocoa/Cocoa.h>
#interface SomeObject : NSObject
#property (retain,nonatomic) id someValue;
#end
#implementation SomeObject
#end
int main()
{
SomeObject *source=[SomeObject new];
SomeObject *target=[SomeObject new];
[target bind:#"someValue" toObject:source withKeyPath:#"someValue" options:0];
[source bind:#"someValue" toObject:target withKeyPath:#"someValue" options:0];
[source setSomeValue:#(42)];
NSLog(#"target: %#",[target someValue]);
[target setSomeValue:#(22)];
NSLog(#"source: %#",[source someValue]);
return 0;
}
As far as I can tell, the problem is the bit [ArrayController selection]. The first problem is that ArrayController is (or should be) a class, and getting the class's selection is probably pointless. The other problem is that even if this were an instance, you would be binding to the selection at the time of the call, which is almost certainly not what you want. You want to track the current selection as it changes.
So what you want is probably something like the following:
[myViewController bind:#"value" toObject:myArrayController withKeyPath:#"selection.isLocked" options:nil];

setString vs. setStringValue in 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.

Resources