On Mar 16, 2013, at 12:46 PM, Chuck Soper <chu...@veladg.com> wrote:

> Are there similar notes on how to use autolayout with NSScrollView? Does
> anyone know the specific details on what Apple recommends? For example,
> are both mixed and pure autolayout approaches okay with NSScrollView? (The
> mixed or pure autolayout terminology comes from the UIScrollView notes -
> link above.)

There is no official recommendation, but I have had a *lot* of experience in 
this area. And I've been meaning to write a blog post about this as a follow-up 
to my Xcoders presentation on auto layout, so consider this a draft!

The biggest issue with using auto layout in an NSScrollView's document view is 
that NSScrollView itself is completely unaware of auto layout. Thus, it relies 
on the behavior provided by setting 
translatesAutoresizingMaskIntoConstraints=YES on its subviews so it can 
continue to use -setFrame: to position them even if auto layout is turned on 
for the window.

At this point it's worth noting another difference between NSScrollView and 
UIScrollView: UIScrollView directly manipulates its own bounds.origin to 
perform scrolling of its entire subview hierarchy. The contentSize property 
dictates the size of the scrollable region.

NSScrollView, on the other hand, doesn't actually do any scrolling on its own: 
it delegates that responsibility to an NSClipView. The scroll view positions 
the clip view and scrollers (if they are visible) using -setFrame:. Rather than 
exposing a contentSize property, NSClipView observes the frame of _one specific 
subview_ called its documentView. This view's frame.size becomes the equivalent 
of UIScrollView's contentSize.

In order to perform scrolling correctly, the documentView's frame.origin must 
lie at (0,0) in the clip view's bounds coordinate system. NSClipView, like 
NSScrollView, is unaware of auto layout, so it uses -setFrameOrigin: to put the 
document view at (0,0). If auto layout gets turned on for the window, this 
position gets turned into a pair of constraints with a priority of 1000 
(required).

Two more constraints will be synthesized to define the width and height of the 
document view. These constraints are the problem. In either direction, one of 
two kinds of constraints will be generated, depending on the documentView's 
autoresizing mask for that direction:

1. If the view is stretchable in that direction, a constraint will be installed 
relating the opposing edge of the documentView to the edge of the superview.

2. If the view is not stretchable in that direction, a constraint will be 
installed dictating the absolute value of the documentView's frame.size in that 
dimension.

Like all autoresizing-mask constraints, these constraints are required 
(priority 1000). Because the entire constraint system is solved at once, it 
should be intuitive that any constraints that attempt to influence the size of 
the documentView will conflict with either the constraints installed by the 
clip view on the documentView and/or with the constraints installed by the 
scroll view on the clip view.

So we have a dilemma. We need to somehow break the bidirectionality of the 
relationship between the clip view and the documentView. There is no 
straightforward way to express this using the constraints API, but it is indeed 
possible without resorting to mucking with private internal details.

In other words, we want to somehow run layout of arbitrary constraints on our 
documentView's subtree and retrieve the resultant frame of the documentView 
without involving the documentView itself in our constraint system. Once we 
have the right values, we can use -setFrameSize: on the documentView; the clip 
view will notice and it will update its scrollable area.

The way we accomplish this is to install another view in our subtree and define 
all our constraints relative to _that_ view. I'm going to call this the adaptor 
view. The documentView installs constraints to keep the adaptor view's top and 
leading margins equal to zero, but critically it does NOT install any 
constraints on the trailing or bottom edges. This leaves the adaptorView's 
width and height free to be defined by its content's layout.

The documentView signs up for frame change notifications from the adaptor view. 
Whenever it changes its frame, the documentView calls [self setFrameSize:] with 
the same size.  Then the clip view hears about this, and the scroll view 
reflects the correct document size. For this to work, the documentView's 
autoresizing mask should be set to width and height NON-stretchable, that way 
when the clip view resizes (perhaps during window dragging) it doesn't resize 
your documentView.

If you're laying out the contents of your scroll view in IB, the most 
convenient approach is to make the adaptor view the only subview of your 
documentView, and to add all your widgets as subviews of the adaptor view. The 
bonus here is that if your constraints are insufficient to fully specify the 
frame of the adaptor view, IB will doggedly insist on adding margin or 
fixed-size constraints until you fix your layout. Yes, we have turned one of 
IB's most annoying habits into a feature!

If you're generating all your constraints in code, however, it might be just as 
convenient to make your adaptor view a _sibling_ of all your widgets and to 
call -setHidden:YES on it, thus removing a level of hierarchy between the 
documentView and your widgets.

> 
> I think that autolayout is a great technology.

Me too, but it will be even better when AppKit fully supports it. All the logic 
I described above could be added to NSClipView, and suddenly NSScrollView would 
be constraint-aware.

Let's see what 10.9 brings.

--Kyle Sluder

_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to