Re: Scroll views

2018-01-19 Thread Quincey Morris
Well, I can’t figure out if IB supports your scenario in Xcode 9.2. In part, IB 
has some bugs in displaying the correct set of constraints error messages. In 
some cases, it displays errors for the wrong view; in some cases, it displays 
out-of-date messages until you rebuild the project, or close and re-open the 
project.

I did remember that Kyle Sluder had posted about this before, and I was able to 
track down this post here:


http://www.cocoabuilder.com/archive/cocoa/326783-guidelines-for-using-autolayout-with-nsscrollview.html#326786
 


The relevant text is:

> 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 

Re: Scroll views

2018-01-19 Thread Jeremy Hughes
> On 19 Jan 2018, at 22:15, Quincey Morris 
>  wrote:
> 
>> Interface Builder complains if I don’t also add vertical constraints, so 
>> I’ve done that, but made the bottom constraint into a placeholder (“Remove 
>> at build time”). Your email suggests that I can also make the top constraint 
>> into a placeholder. I’ve tried that now, and it seems to work fine. It also 
>> makes sense :)
> 
> Using placeholder constraints makes no sense to me.
> 
> First of all, you need to understand that in current versions of Xcode, IB 
> translates those resizing control arrows into constraints for you, unless you 
> add *explicit* constraints that it regards as overriding this “free” 
> translation. This means that those arrows are *not necessarily* honored. If 
> you’re getting messages about missing constraints, then that means you’ve 
> added some subview constraints that prevent the document view height 
> constraint from being inferred.

Once you add explicit constraints, IB turns off autoresizing altogether. The 
red arrows/lines disappear - there’s nothing there to be honoured or not 
honoured.

In my case, IB doesn’t know about the “document" height because it’s determined 
by a subview that is in a separate nib. That’s why I need to use placeholder 
constraints (otherwise IB complains about missing constraints). There are 
several subviews, and they are swapped in and out at runtime.

> — You don’t want IB to provide default constraints on the document view. You 
> should probably write code to remove any existing constraints from the 
> document view before you swap in new subviews. (Even if you turn off all the 
> arrows in the IB resize control, it’s not obvious to me that IB won’t add a 
> height constraint anyway.)

You don’t need to turn off the red arrows/lines because they disappear as soon 
as you add explicit constraints. Without autoresizing, IB doesn’t provide any 
default constraints. I don’t think you should ever have to remove default 
constraints in code - you should turn off autoresizing (in IB or in code) to 
prevent the default constraints from being created in the first place. 

I think I mentioned previously that constraints are added to the subviews at 
run time - and, yes, they are removed and recreated when subviews are swapped.

It is the subview that determines the width of the “document” view, so the 
horizontal (explicit) constraints pinning the “document” view to its superview 
are required. They perform the same task that the horizontal red arrows/lines 
do with autoresizing (prevent horizontal scrolling). I can’t use autoresizing 
here because it will create additional constraints that generate conflicts with 
subview constraints.

Jeremy
___

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


Re: Scroll views

2018-01-19 Thread Quincey Morris
On Jan 19, 2018, at 12:36 , Jeremy Hughes  wrote:
> 
> If you add a scroll view in Interface Builder, that’s how it is set up for 
> you by Interface Builder. You don’t have to add an autoresizing mask or use 
> it to pin the document edges - because Apple has already done that for you! 
> So you’re confirming my view that it’s confusing.

IB doesn’t know what you intend, so this is a case where you do need to go in 
and change the settings to what you need. In this cases, there are 3 things to 
think about:

1. You need to constrain the NSScrollView to (usually) fill up the view it’s 
in. You can do this by turning on all 6 arrows inside the resize control in the 
inspector.

2. You can only edit the outer 4 arrows of the NSClipView but you generally 
shouldn’t change the default setting at all.

3. For a document view that scrolls vertically, turn on the 3 horizontal resize 
arrows, turn off the 3 vertical ones. (I was wrong before, you *do* have to 
turn on the center one. I believe this is different to how this worked in 
earlier versions of Xcode.)

Because of #3, the document view height is now whatever it’s created at in IB, 
which by default is the same size as the clip view. You must set it to whatever 
you really need, in IB or later in code.

For example, I created a project and put a scroll view inside the root content 
view. The default height was about 250, so I tried making the document view 500 
instead. By putting some subviews inside this document view, I was able to see 
that the view scrolled correctly when the app runs. Resizing the window did 
what seemed to be a reasonable thing (the top left visible pixel before the 
resize stayed at the top left of the visible rect during the resize).

All of this is exactly what I would expect, and you should be able to reproduce 
this behavior.

> I mentioned in my follow-up email that I need to use explicit constraints in 
> order to avoid conflicts between autoresize constraints and the constraints 
> I’m adding to a subview. In order to avoid horizontal scrolling I’ve added 
> explicit constraints to pin the left and right edges of the “document” view 
> to its superview.

Yep, that part is the same as #3 above.

> Interface Builder complains if I don’t also add vertical constraints, so I’ve 
> done that, but made the bottom constraint into a placeholder (“Remove at 
> build time”). Your email suggests that I can also make the top constraint 
> into a placeholder. I’ve tried that now, and it seems to work fine. It also 
> makes sense :)

Using placeholder constraints makes no sense to me.

First of all, you need to understand that in current versions of Xcode, IB 
translates those resizing control arrows into constraints for you, unless you 
add *explicit* constraints that it regards as overriding this “free” 
translation. This means that those arrows are *not necessarily* honored. If 
you’re getting messages about missing constraints, then that means you’ve added 
some subview constraints that prevent the document view height constraint from 
being inferred.

What you actually do about such messages depends on what size you want for the 
document view. 

— If you want a one-time fixed height, you should add an explicit height 
constraint. 

— If you want a fixed height (that is, not derived from the subviews by 
auto-layout) that changes at various times according to decisions made in code, 
you should add an explicit height constraint and modify the constraint at 
run-time. 

— If you want a height that is derived from the subviews, you need to add 
sufficient constraints within the document view, and between the document view 
and its child views, to satisfy auto-layout’s requirements. In this case, there 
should be *no* vertical constraints relating the document view to its superview.

If your application is swapping subviews into the document view at run-time, 
then you have a couple of points to remember:

— You don’t want IB to provide default constraints on the document view. You 
should probably write code to remove any existing constraints from the document 
view before you swap in new subviews. (Even if you turn off all the arrows in 
the IB resize control, it’s not obvious to me that IB won’t add a height 
constraint anyway.)

— The document view’s vertical auto-layout universe is self-contained. You need 
to ignore the clip view or any other ancestor views, but make sure you 
establish a complete set of vertical constraints between the document view and 
its children. Whether that resizes or preserves the document view height is up 
to you.

___

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 

Re: Scroll views

2018-01-19 Thread Quincey Morris
On Jan 19, 2018, at 04:28 , Jeremy Hughes  wrote:
> 
> Summarising: it seems that to get a vertically scrolling view that works as 
> expected, the content view must be set to be flipped (no way of doing this in 
> IB) and the flexible-height arrow must be turned off. The top-margin line can 
> also be turned off, but it actually makes no difference if it is on or off.
> 
> Does anyone have a way to make sense of this or is it just inherently 
> confusing?

In general, you should *not* use autoresizing masks on the document view, 
precisely because the point of a scroll view is that the document view’s size 
is *unrelated* to the size of the piece of window real estate (the clip view) 
in which it is displayed. This is made worse if you also use autoresizing masks 
to pin the document view’s edges to the clip view, because the point of 
scrolling is to change the position of the document view origin relative to the 
clip view origin. Pinning the origin just conflicts with that.

IOW, you should set the size of the document view absolutely, either one time 
initially or whenever the size needed to encompass the contents changes. You 
may or may not need to set the position of the document view relative to the 
clip view initially or whenever the document view size changes, depending on 
whether the default behavior is what you want.

All of that is true for using auto-layout constraints, too.

However, in the case of a view like a text view which (for horizontal writing 
systems) can wrap its contents to a fixed width, it’s common to pin the 
left/right edges of the document view to the left/right edges of the clip view. 
(In terms of the autoresizing control, that means turning on the outer arrows 
on the left and right sides, and turning off the inner horizontal arrow.) In 
that case, there is never any actual horizontal scrolling, because the two 
views are the same width.

So:

1. Don’t pin any of the edges of the document view to the clip view, except 
when you want the widths to match.

2. Set the height of the document view manually to the size that encompasses 
all of its scrollable content. You can do this one time in IB, one time in 
code, or whenever the needed size changes, depending on how your app works.

___

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


Scroll views and autolayout

2018-01-19 Thread Jeremy Hughes
This is a follow-up to my previous email on Scroll views.

What I actually want to is be able to swap different views (with different 
heights) in and out of the scroll view. To do this, I’m adding the different 
views as a subview of the “document" view (called contentParent below):

contentParent.subviews = [subview]

I then add constraints that pin the edges of this subview to the edges of its 
parent.

Unfortunately, this creates autolayout conflicts when autoresizing is used with 
contentParent, since the autoresizing mask has created a fixed height for 
contentParent, and this fails to match the height of the subview.

It seems that I can fix this by using autolayout on the document view 
(contentParent), so I’ve added constraints that pin the left, top, and right of 
contentParent to its superview. In order to keep IB happy I’ve also added a 
placeholder (“Remove at build time”) constraint that pins the bottom of 
contentParent to the bottom of its superview.

This seems to do what I want. I’m slightly concerned about the fact that the 
superview of contentParent is a clip view, because we don’t actually want the 
edges of contentParent to be pinned to the clip view (or it wouldn’t actually 
scroll within the clip view) - but I assume that there is some kind of magic 
that goes on behind the scenes when constraints are added to views that are 
enclosed within scroll/clip views (although I haven’t seen this discussed 
anywhere).

Jeremy

___

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


Scroll views

2018-01-19 Thread Jeremy Hughes
Does anyone understand scroll views? I struggle every time I use them, and it 
seems that I have to go through a process of trial and error to get them 
working properly.

If I drag a scroll view into another view in Interface Builder, I get a 
“Bordered Scroll View” that contains a “Clip View” that contains a “View” - 
which seems fine. The clip view will clip the content (“document”) view. So I 
can see what’s happening, I create a subclass for the document view, which 
looks like this (for debugging purposes):

class ContentView: NSView
{
//  override var isFlipped: Bool { return true }

override func draw(_ dirtyRect: NSRect)
{
super.draw(dirtyRect)

NSColor.red.set()
NSBezierPath.stroke(self.bounds)
}
}

isFlipped is commented out at this stage, but I’ve added it in case I need it 
later.

The document view’s class is set to ContentView in the Identity inspector in 
Interface Builder.

By default, Interface Builder gives the content/document view an autoresizing 
mask where all the red lines are turned on in the Size inspector - meaning that 
the width and height are flexible, and also (I think) that the margins are not 
flexible. What happens when I build and run without changing this is that the 
content view resizes to match the parent view - so the scroll view is 
completely redundant (nothing ever scrolls). That’s fine for horizontal 
scrolling, which I don’t want, but I do want to have vertical scrolling, so I 
turn off the adjustable height arrow.

What I now have is a view that scrolls vertically, but is pinned to the bottom 
of its parent view. Uncommenting isFlipped in ContentView fixes this problem, 
and I think everything is now working correctly. I’ve looked for a flipped 
setting in Interface Builder, but I haven’t found it, so I assume it’s missing 
for some reason that I don’t understand.

But there are other alternatives that seem confusing. Before turning on 
isFlipped in code, I tried turning off the bottom margin red line in Interface 
Builder. This gives me a view that is still pinned to the bottom of its parent 
view - but when the window is shrunk smaller than the height of the content 
view, the bottom rather than the top of the content view is scrolled out of 
view. If I turn isFlipped on at this point, I get a content view that is pinned 
to the top of its parent view (good), but when the window is shrunk smaller the 
top of the content view is scrolled out of view (not good). If I go back and 
turn the bottom red line back on and turn the top red line off (in IB), it 
works correctly.

Summarising: it seems that to get a vertically scrolling view that works as 
expected, the content view must be set to be flipped (no way of doing this in 
IB) and the flexible-height arrow must be turned off. The top-margin line can 
also be turned off, but it actually makes no difference if it is on or off.

Does anyone have a way to make sense of this or is it just inherently confusing?

Jeremy

___

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