Padding around an NSTextView in an NSScrollView - cocoa

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.

Related

How to implement a horizontal rule in a text view?

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.

How to call NSScrollView autoscroll-method programmatically

I have simple chat application with text messages view-based NSTableView as you can see at the picture below.
Each message contains NSTextView instance having height to fit all the text.
All I need is to start NSScrollView (which NSTableView-instance is enclosed by) autoscrolling while the user selecting text dragging mouse far enough. Unfortunately, autoscrolling doesn't appear. In case of dragging somewhere outside of the text views all succeed.
I tried to call autoscroll:-method directly by simply push NSEvent-instance from NSTextView-subclass "mouse dragged"-event (like in example from this article):
- (void)mouseDragged:(NSEvent *)event
{
[self.scrollView autoscroll:event];
}
As I've overrode all the mouse events and implemented all the text selecting, this method often invokes. But the autoscrolling doesn't seem to work.
UPDATE
I figured out that before calling -autoscroll:-method there must be -mouseDown: of the same object. But it breaks my text selecting mechanism. The point even not in being first responder, there must be nothing but the mouseDown:-method.
Normally, a text view is within a scroll view of its own. Even if that's big enough to show all of the text without scrolling, it's still there. A call of -autoscroll: on anything within that scroll view (possibly including that scroll view itself?) will just try to scroll that scroll view, not the scroll view that contains the table view.
Try calling -autoscroll: on a view higher up in the hierarchy. Either self.scrollView.superview, the table cell view, or the table view.
Note, though, that the table view's scroll view will keep scrolling even after the cell view containing the text view is fully on-screen. In fact, it may keep scrolling it so far that it's off the screen in the other direction. Basically, it doesn't know that you're trying to select within the text view so it doesn't know to stop when the selection extends all the way to the edge of the text view.
Another approach might be to try to use a "bare" text view with no enclosing scroll view. I don't think IB will let you do that, so you'd have to do it programmatically. Bare text views don't play well with auto layout, though.

How do you use NSScrollView with auto layout?

Ok. I've been at this over and over. I've seen blogs and cocoa dev threads.
I've seen Kyle Sluder's proposed solution, but have yet to find a solution that really works.
How can you position subviews of an NSScrollView with auto layout?
Is it just silently broken ?
Nothing seems to work.
Ok, old question, but this particular issue is a personal bugbear of mine so I'll answer it anyway!
The first thing to note is that an NSScrollView contains an NSClipView, which itself has a view outlet called documentView. These are all added for you when you drag a new scroll view into your storyboard or nib file. By default, the document view is an NSView called simply "View". If you're using a custom view, you can just select this and set its type in the inspector on the right to whatever you want. Otherwise, you'll be adding subviews to it.
The big thing that is easy to miss here is that, by default, the document view has its layout set to 'Translates Mask Into Constraints'. This is fine if the content size will never, ever change, and if that's the case you can simply set the frame of the document view to whatever you want and leave it at that. If you want it to automatically resize itself to fit its content however, there's a few things you'll need to do.
First off, that document view needs to have a completely unambiguous size. If you're using a custom view, I'd recommend giving it an intrinsicContentSize. You should also set 'Intrinsic Size' in IB's inspector to 'Placeholder' and give it a suitable value, or you'll get a bunch of autolayout warnings. If your document view gets its size from its content, all of the subviews must be linked in an unbroken chain from top to bottom, and from left to right, such that the content knows exactly how big it ought to be. This is quite an art in itself, so I won't go into it. A simple example where you have only one subview would be to pin its top, bottom, leading and trailing constraints to its parent, but as noted above if you're doing this, you might as well just set the type of the document view.
Now the fun bit. Select your document view and set its layout to 'Automatic'. Next, add top, bottom, leading and trailing constraints to its superview with a suitable value. I'm using zero, but you might want a small border. Finally, select the TRAILING and BOTTOM constraints you just made and set them to '>=' (greater than or equal) and a priority of 500 or less. The priority is very important, as it has to be less than the priority that the clip view uses to determine its own minimum size. Too high and the clip view will be forced to remain larger than its content, making it impossible in turn for the scroll view to be smaller than its content, rendering it useless.
The technical details aren't important. Just remember to set the document view to layout: automatic, pin all edges, and make the trailing and bottom constraints >= and priority 500.
Note that this will cause your content to hug the top-left corner.
Have you tried setting the document view's setTranslatesAutoresizingMaskIntoConstraints to TRUE?
[_scrollView.documentView setTranslatesAutoresizingMaskIntoConstraints:YES];

Subview that expands with window using NSAutolayout

I'm trying to learn auto layout so I can set up a moderately complicated display the way I want. I'm starting with a simple version. At least I thought it was simple.
I have a content view containing a NSScrollView, and a zoom slider. The scroll view is, of course, just a window into a larger 'canvas' on which the user can do things.
I'd like the scroll view to be as big as the window allows, with the slider underneath.
I've tried many things none of which work, in some cases when I resize the window smaller, the scroll view goes on up over the window's top bar, obscuring the title and the red yellow, green, dots.. this is just a grumble, I won't attempt to describe how I got it.
I'm working with Visual Format Language.
The immediate problem: I can only get the thing to work at all if I put in a hard size constraint on the scroll view.
I've got constraints like #"V:|[ScrollView]-[ZoomSlider(==35)]-| and
#"|-20#1000-[ScrollView]-|"
With these, nothing shows at all, until I put a hard size on the scroll view:
For example, #"V:[ScrollView(>=70#20)]" and #"[ScrollView(>=140#20)]" results in a little tiny scroll view (as expected) just above the slider.
Window is resizable, all right.
Is there a simple way to make the scroll view resize to occupy the most space possible when I resize the window? The only way I can think of off hand is to produce metrics for the scroll view based on window size, and use a notification to change the constraints when the window size changes. There should be something simpler!
THanks.ee
OOps. Thought I knew the answer until I started to write it.
AT least here is a partial explanation of things that were causing me problems.
You can't add constraints that position or size the Window's content view. But apparently you can mess them up by deleting them programatically. Some of my problems were solved by getting rid of
[self.myContentView setTranslatesAutoresizingMaskIntoConstraints:NO];
and
[self.myContentView removeConstraints:self.myContentView.constraints];
This left me with a lot of conflicting constraints. I fixed this be eliminating all content window constraints that I could in IB, then by marking the rest as placeholders.
I've got a ways to go before I understand how to use auto layout, but doing it in code is easier (for me) than doing it with IB

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.

Resources