Enabling NSScrollView to scroll its contents using Auto Layout in Interface Builder

Here's the general approach:

  • Make the document view at least as tall as the clip view. Or, equivalently, make the clip view no taller than the document view.
  • Allow the document view to grow in height, but not shrink beyond what its subviews require.
  • Prevent ambiguity in the document view by having a low priority constraint to make it as small as possible given the other constraints.

So, for example, there should be a constraint between the clip view's bottom and the document view's bottom, but it should be an inequality: Superview.Bottom <= Document View.Bottom. (Or, equivalently, Document View.Bottom >= Superview.Bottom.)

Within the document view, you probably have some text field or something at the bottom and a constraint between that and the document view's bottom. Make that constraint an inequality: Superview.Bottom >= Text Field.Bottom + standard spacing.

That will cause ambiguity as to the height of the document view. It could be any size tall enough to fit all of its subviews. Add a height constraint. Set its priority to 51 and its constant to 0. That is, it wants to make the view have 0 height, but at very low priority so almost anything else will supersede it. But it resolves the ambiguity.

If you want to allow horizontal scrolling, you need to do the same general thing in the horizontal orientation.


Update:

There's another approach. Configure the constraints within the document view to give it a strict size (no inequalities). That's usually a chain of constraints from its top to the top subview, from that subview's bottom to the top of another subview, etc., and from the bottom of the bottom subview to the document view's bottom. Same for leading to trailing.

Then, the only necessary constraints between the clip view and the document view are top and leading constraints.

If you test in this configuration, you'll be able to resize and the scroll view will scroll. So, that's good. However, when the scroll view's content area is taller than the document view, the document view will be pinned to the bottom of the content area. You usually want it pinned to the top in that situation.

The reason is that the clip view is not flipped. Also, it's adjusting its bounds to match the document view. So, even though there's a constraint to keep the document view pinned to the top of the clip view, the top of the clip view isn't where you expect it to be. The clip view puts the document view at (0, 0), which is at the bottom.

So, the final piece is to create a subclass of NSClipView that overrides -isFlipped to return YES. Then, set the class of the clip view in the NIB to your subclass. After that, it will work as you want.


Building on the answer from Ken Thomases, with Swift 4, one can add flipped as a User-Defined attribute to NSClipView, to pin the contents to the top of the scrollview.

flipped attribute