Autoresizing NSTextView and it's Font Size - objective-c

I'm trying to make my NSWindow autoresizable. I've gotten most of my UI items to resize, but one of the few remaining objects that pose issues are NSTextViews: indeed, I can't find a way to calculate the new font size once the NSTextView has been resized.
For NSTextFields, I had found a method that would find the font size based on the length of the text. Apparently, there doesn't seem to be an equivalent method for multi-line text containers (unless I just haven't found it).
The only actual place I found that mentionned something of the sort is here: http://boutcher.tumblr.com/post/7842216960/nstextview-auto-resizing-text
However, I wasn't able to implement this code into my application, as there seems to be an error I can't fix with the way an NSLayoutManager is created.
Has anyone done this in the past? I'm considering just allowing the user to resize to just one size, so I can hardcode the font size... It's a real pain dealing with these NSTextViews !

See the sizeWithAttributes: method in “NSString Application Kit Additions Reference.”
It returns an NSSize, which you can compare to the textview’s current frame.size.
For the “Attributes” arg make an NSDictionary with an NSFont as the object and NSFontAttributeName as the key. (Don't be confused by that constant. It looks like it's a key for a string, but it is not; it is a key for the font itself.)
Get the string from the textview: [[yourTextView textStorage] string].
Get the familyName of the font you are using and its current point size, a CGFloat. Compose fonts to test using the constructor [NSFont fontWithName:familyName size:floatChanged].
Once you've arrived at the correctly sized font, use it to make a new NSAttributedString out of the old string. Just use the "attributes" dictionary you made above (the one that produced the correct size) and feed it to NSAttributedString's initWithString:attributes constructor.
Assign that attributed string to the textStorage (itself a subclass of NSMutableAttributedString): [[yourTextView textStorage] setAttributedString:thatYouJustMade].
Note: If the textview employs attributes like underlining or fore/background coloring, or italicized/bold fonts, you must get that information from the textStorage and incorporate it into the new attributed string. It's doable. Look at the class refs for NSAttributedString and NSMutableAttributedString.

Related

Appending *simple* ASCII strings to an NSScrollView

Is there a simple way to append a string to an NSScrollView's NSTextView? I don't want attributes. I simply have error messages coming in as NSString's, and I wish to append each to the window. Nothing fancy. No formatting beyond CR, LF and perhaps TAB if I have too many beers and decide to get over the top fancy.
Every path I follow through the Class docs seems to lead down into a self referential blackhole... Like NSMutableAttributedStrings... which aren't really NSStrings, and don't even have a cString method.
I have been considering just keeping my own NSString and complete rewriting the contents of the scroll view after appending the errorstring the easy way. But that seems... inefficient... when the numbers of reports could get quite large.
Every string needs some attributes to be drawn to the screen — a font/size/colour at the very least is mandatory.
It's not entirely obvious from the docs, but the "proper" way to manipulate an NSTextView is by manipulating the NSTextStorage directly. Also, NSTextStorage is a subclass of NSMutableAttributedString.
You can add characters to the string without dealing with attributes, it will simply copy the attributes from the string around where you add the text to:
[textView.textStorage replaceCharactersInRange:NSMakeRange(textView.textStorage.length, 0) withString:#"\nhello world"];
You will probably also want to scroll down:
[textView scrollRangeToVisible:NSMakeRange(textView.storage.length, 0)];
Performance will be good, even up to gigabytes of data. NSTextView is very efficient, especially when only small changes to the content are made.

How to insert a NSButton into a NSTextView? (inline)

I have a NSTextView and a custom NSButton. What I want to do is to insert that button (60x16 in size) to the end of the NSTextView.
How can I do something like that? I've been trying to search around on how to do this but I'm not getting anywhere.
Where should I begin? Thanks
I believe this question is pretty similar to yours:
Buttons inside an NSTextView / NSTextStorage
Quote from the question:
how do I get an NSButton to appear inside the text and react to
clicks?
Note that the issue is not fully solved there, but it seems the OP got a good head start. Hopefully you can take some clues from the discussion.
Here is one answer:
NSTextAttachment holds the contents of an attachment; it is the value
of the NSAttachmentAttributeName attribute for the
NSAttachmentCharacter in the attributed string. The contents are
usually given by an NSFileWrapper, but this is not required; you can
create an empty NSTextAttachment with a nil file wrapper.
NSTextAttachmentCell handles display and user interaction for the
attachment. By default an NSTextAttachment will create an
NSTextAttachmentCell to display itself, depending on the contents of
the attachment; in the generic case this will just be an image cell
displaying an icon.
If you wish, however, you can supply a custom NSTextAttachmentCell for
your attachment. It need not be an member of the class
NSTextAttachmentCell; it only needs to conform to the
NSTextAttachmentCell protocol. Actually, even that is not strictly
necessary; it only needs to implement a few of the basic methods for
sizing and drawing. Most cells already do this.
You will, however, need to deal with mouse events yourself. The
methods you'll probably want to implement would be
wantsToTrackMouseForEvent:inRect:ofView:atCharacterIndex: and
trackMouse:inRect:ofView:atCharacterIndex:. The character index here
should let you determine which portion of the text is relevant.

Keep NSTextView's Font From Changing

I thought this would be straight forward, but it looks like I was wrong. Basically, all I'm trying to do is keep the font from changing to the Apple default: Helvetica Regular 12pt.
I've made a subclass of NSDocument and in my implementation file I have the following method:
- (void)windowControllerDidLoadNib:(NSWindowController*)aController
{
[super windowControllerDidLoadNib:aController];
if(attrString)
{
[[textView textStorage] setAttributedString:attrString];
[[textView textStorage] setFont:[NSFont fontWithName:#"Menlo Bold" size:24]];
}
This method works all right when I open a file, but if I delete all of the text and then type again, the font resets to... Helvetica Regular 12pt... All I want is to keep the font and size as I specified it for the entire life of the program.
You need to set the typing attributes of the text view to contain your font for the key NSFontAttributeName.
However, I would go a step further. If you know you NEVER want a certain font in your model (NSTextStorage -- the backing store for NSTextView), simply subclass NSTextStorage and override the attribute setters and getters. NSTextView gives the user access to font menus and copy/paste will still allow certain fonts in. The only way to truly guarantee it never enters your text view is to never allow it into the model.

Maintain Undo In Modified, Bound, NSAttributedString

I've got an attributed string bound to a NSTextView. I'm using a method that is called (using KVO) every time the string is edited to add background color attributes to string based on a regEx match. I do this by creating a new mutable attributed string with -initWithAttributedString: then -beginEditing, -addAttribute:, -endEditing. Once I've added all the background color attributes I want, I call the string's setter [self setTextViewString:mutableAttributedString] The issue is, that if there actually are any attributes added to the string, it kills undo and moves the cursor to the end of the string.
How can I maintain undo? I've maintained cursor position by calling the textView's selectedRanges and setSelectedRanges: methods on either side of the setter, but this still seems a bit hackish.
I wasn't able to bind the textview directly to the mutableattributedstring, but it seems like there should be a more direct way to modify the bound string so it doesn't mess up editing.
PS, the addition of attributes happens after the KVO method finishes by calling -performSelectorOnMainThread: It was the only way I could get the added attributes to display.
[self setTextView:mutableAttributedString]
Pardon? You're setting your textView to an attributed string? Wouldn't it make more sense to keep your text view there?
Try getting the text view's textStorage and replacing its contents with the new attributed string by sending the text storage a setAttributedString: message.

NSColor with calibrated values works differently than regular color?

I'm using a method in my view to set a color, and in awakeFromNib I pass it a color using
[NSColor colorWithCalibratedRed: green: blue: alpha:]
The application kept crashing, with the error with "[NSCFNumber set] unrecognized selector."
After inserting a breakpoint, I found it was defining my variable as an "NSCalibratedRGBColor." The application worked when I defined the color with one of the convenience methods (blueColor, whiteColor, etc.). I thought those were just a shortcut for setting RGB values. I have no idea why I haven't run into this problem before, I've used colors like this a lot. Why does it handle this differently, and can I make it interpret it as a regular color?
EDIT:
The code is: [self setLineColor:[NSColor colorWithCalibratedRed:green:blue:alpha]; in my awakeFromNib. I've also discovered that it is a non-1 alpha value that causes the color to be defined "NSCalibratedRGBColor."
Alpha values of 1, like the convenience methods, cause the color to be defined "NSCachedRGBColor" in the debug, which works completely fine.
The application kept crashing, with the error with "[NSCFNumber set] unrecognized selector."
That means that you over-released the color, and then another object (in this case, an NSNumber) got allocated to the same pointer. Then you sent the set message to the object that you thought was your color, but it was actually now an NSNumber object. Result: That error. It had nothing to do with your use of a calibrated vs. uncalibrated color space.
All the colors support the same NSColor interface. The NS[snip]Color classes you're seeing are private subclasses of NSColor; they all support all of NSColor's methods. As far as you're concerned, they are all just NSColors.
Named colors like +[NSColor blueColor] are singleton objects. But +[NSColor colorWithCalibratedRed:...] are not. It sounds like you are not properly retaining the color.
Make sure to read the memory management documentation for retain/release. Or just switch to garbage collection.
What was the line of code that was causing a problem?
NSCalibratedRGBColor does work differently than a non-calibrated color, in that when you "set" it, the color will be calibrated to the context's colorspace, rather than using the raw uncalibrated color.
However, I've never seen this error on this particular usage of NSColor methods, and without further information, I'm left scratching my balding head.