How to implement a horizontal rule in a text view? - cocoa

Say I have an NSTextView (or a UITextView on iOS) and I want the user to be able to insert horizontal divider rules, like the HTML <hr> tag or this thing:
What's the best way to implement this?
Apple's documentation is very thing on this. So far, I have two ideas:
Insert an NSTextAttachment for each rule and make the layout manager draw it somehow.
Instead of a single text view, use multiple text views with scrolling disabled, put them in a stack view, add separator views between them and then put the stack view in a scroll view.
Both approaches seem a little wrong or inelegant to me because:
From the documentation, it sounds like text attachments are intended for attaching files in the first place. A horizontal rule is not a file.
If I use multiple text views, I'll probably lose some performance tweaks inherent to NSTextView as all the text views need to be loaded and ready for display, no matter where the user has scrolled. In addition, the multiple text view approach would prevent the user from selecting the entire text, which is a requirement in my app.
Any better ideas?

I remember trying to do this years ago. Using NSTextAttachment was the method that worked for me. I bookmarked this conversation with Mike Ferris to help me remember how to do it. I don't have the code anymore, but it was pretty straightforward. I subclassed NSTextAttachmentCell which conforms to NSTextAttachmentCellProtocol. You override cellFrame(for textContainer: NSTextContainer, proposedLineFragment lineFrag: NSRect, glyphPosition position: NSPoint, characterIndex charIndex: Int) which gives you access to its container that can provide its width, and it has the line fragment which gives you the y position of the text above your divider (plus any padding you want). Then just override the draw(withFrame cellFrame: NSRect, in controlView: NSView?) method and draw your divider.
As for the payload NSData, you could do anything with that. Maybe you include the width of the line, it's padding, color, etc? The good thing about using NSTextAttachment is that it stays embedded in the text itself like the <hr> tag.

Related

Adding controls to NSTextView and binding them to (ranges of) characters

When editing code, Xcode is capabale of displaying in-text controls, like drop down buttons which can show context menu's. I've seen other OS X apps that handle text capable of similar features. See the attached sample.
I presume this effect is obtained using NSTextAttachmentCell - although I'm not sure whether this is the proper way to implement this.
For my own app I would like to use this technique as well.
I have the following questions:
Is NSTextAttachmentCell the correct way to implement such a feature? If not, what would be?
How do I attach a control -comparable to the one in the above sample- to a specific range of text so that its location within NSTextView is dynamic and follows layout actions?
I found this which gives some hints but does not cover the attachment to specific text ranges.
Although NSTextAttachmentCell will work, it has a disadvantage: the cell will become just a glyph in the text which was not what I wanted. It distorts the layout of the text, is selectable etc. I wanted the cell to be drawn over the text, just like the behaviour in Xcode.
The challenge was to find a way of translating a point from a Mouse Moved event to the position of a particular string of characters inside the NSTextView.
After some more digging I found a little gem in Apple's demo apps called LayoutManagerDemo. This demo shows a custom subclass of NSTextView capable of highlighting individual characters, words and lines while the mouse is hoovering its view. From there on it was pretty easy to fade in a button at the required NSPoint and then show a popup menu with some options.

Custom drawing selection in NSTextView

I've got a NSTextView where I'd like to control drawing of the highlight/selection. Anyone know which method I have to overwrite so I can control what to draw in the selection rect?
You do not specify what you want to draw, so it's not easy to give you a straight answer.
If it's just an attributed string (Font, Style, Color, Background), you do not need anything fancy, just look for NSAttributedString. There are methods like -setSelectedTextAttributes: and the delegate method -textViewDidChangeSelection:
Generally, you should not subclass NSTextView if you don't have a very good reason to do so. You can do almost anything by just utilizing the usual delegation mechanisms.
If it's something very customized, there is a variety of possibilities depending on if you want to customize the selection (-setSelectedRange:) draw an overlay view (get the frame of the selectedRange) or mess around with the low level layout engine (-layoutManager). It really depends.
If you don't want to use the standard attributes to highlight text, eg by using an CALayer, you can get the rects containing the selection from the layoutManager of the NSTextView.

Text Field rendering

I am trying to build a really simple NSTextField with Interface Builder (XCode 4), but the rendering is really weird with default values:
The only setting I changed is the border style:
My question:
How to display a neat Text Field “squared but with rounded corners”, like in Safari:
How to remove that “overflow:hidden” (sorry for the CSS description) which cuts the focus? < Interface Builder bug, fixed.
Should I design my own, image-based component?
Thank you!
I think I've found exactly what you're looking for. Here's what it looks like:
It's called SSTextField. Download the subclass here: http://cocoatricks.com/2010/06/a-better-looking-text-field/
What you've got at the top is a NSSearchField, which is designed for filtering/searching.
Likely the reason why the focus ring is cut off is because you've got it inside a box or overlapping another object. Don't do that.
There are no standard rounded-corner (as opposed to rounded-end) text fields; if you want one, you'll need to subclass NSTextField yourself, or just wait for Lion where the standard text field will have rounded corners.
Rounded rectangle text fields are pretty straightforward and don't require subclassing the control. Instead you can simply override the way the background CALayer of the control is drawn.
Choose the square-cornered field shape, add the QuartzCore framework to your project, and then #import <QuartzCore/QuartzCore.h>. In your controller's viewDidLoad method you'll modify the text field's layer's cornerRadius property, a la:
myTextField.layer.cornerRadius = 6.0;
Poof, rounded-rectangle text field!

How can I get an NSTextField to behave like a HTML text?

I have some static text that is usually only 1 line long that displays in a NSTextField in my xib. In some instances, the text is long enough to warrant 2 lines, and I just want the label to resize vertically to fit it, without giving me scrollers or any thing else. Think of how text on a webpage behaves… that is what I want. I just want the label to grow shrink with different text, and with adjustments to its width. How can I achieve this?
UPDATE
Here is a video of how it currently behaves: http://screencast.com/t/4JYTv7jVG3O
Notice how when the NSTextField is two lines long, there is a big gap underneath the text. This is because the stars and button are aligned at the bottom of the frame, and because I have to have the frame taller to accommodate 2 lines, they stay there. If I can get an answer to this question, I would make the frame shorter for the 1 line text, and make the bottom textfield (with the smaller text) taller to compensate. Can this type of floating layout be done?
One option is to actually use a WebView to display your content. You will then get exactly the behaviour you are expecting, at the cost of a bit of work to manage interaction with the controls.
You would need to set the WebView to display no background, using [webView setDrawsBackground:NO].
You'd also need to construct the content (including the star rating and the button) using HTML/CSS and then use the Objective-C/JavaScript bridge to call back to your app when the button is pressed.
More information on calling Objective-C from JavaScript is here.
You could probably also use an NSTextView and embed the button and star rating as NSTextAttachment objects but this is quite complex, it would be a lot easier to use a WebView.
The only other alternative that I can see is writing a view controller that manages the layout of the controls based on the current size of their container view. You would need to measure the text to do this and one way to do that is to use the excellent NS(Attributed)String+Geometrics category written by Jerry Krinock.

Padding around an NSTextView in an NSScrollView

I'd like to provide a few pixels of padding around an NSTextView inside of an NSScrollView. I've reviewed this post and using setTextContainerInset does the trick for the left, right, and bottom margins. But as soon as the text scrolls the top buffer is lost. Same thing with overriding textContainerOrigin. Another answer in that post states:
The way TextEdit does it (when in Wrap to Page mode) is to put the text view inside of a larger view, and set that larger view as the document view of the scroll view.
But if I do that (using, say an NSBox) the content no longer scrolls. Am I missing something regarding that particular trick, or are there any other techniques that folks could suggest?
There's a step that seems to be missing from your quote. You'll need to make sure your new document view tracks the changes in the text view's frame and sizes itself to fit. You can turn on NSViewFrameDidChangeNotification on your text view with -setPostsFrameChangedNotifications:, then have your new document view listen for notifications from your text view..
By the way, I ended up accomplishing this by subclassing NSClipView, overriding setFrame:, setFrameOrigin:, and setFrameSize, and hacking the origin and width in those methods to add my padding.

Resources