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];
Related
I think I have arrived at a decent understanding of how auto layout works in Interface Builder (ha). I've got a .xib that says "No auto layout issues". Here's two screenshots of it, with nothing selected and with things selected so you can get a sense of the constraints at work:
Now I would like to, e.g., decrease the vertical spacing between the "Total thumbnail size:" label and the "Use TIFF" radio button matrix. So I click on things to get that constraint selected, and I edit the "Constant" value of it to change it from 47 to, say, 30. When I do so, I expect that the window will shrink vertically by 17, so that all constraints continue to be obeyed. Instead, it shifts the vertical position of the "Size & format" label, breaking its vertical constraints in so doing, and then complains about "Content priority ambiguities" involving a bunch of items; here are screenshots of that state, showing the priority ambiguities:
It wants me to change vertical compression resistance priorities and/or vertical hugging priorities to allow it to compress/expand something away from its intrinsic size; but I don't want to do that. I want all of those items to retain their intrinsic size, and I want the window to resize. I can resize the window manually, by -17, and move the various items into their proper positions, and then it says "No auto layout issues" again; so there is really nothing difficult or ambiguous about this situation except that for some reason it refuses to resize the window as it ought to. So, my question is: how do I avoid it doing this, and have it just resize the window and move things properly on its own? I don't understand why it's so confused; it seems to me that there is a clear, unambiguous chain of vertical constraints from the top to the bottom of the window, and changing one of those constraints ought to simply force the window to resize. I've tried using "Update Frames" but it doesn't seem to help.
I think I have uploaded the .xib file, prior to the constraint change, to my Dropbox here: https://www.dropbox.com/s/vp90s1fh692i8pi/CopyThumbs.xib.
I grabbed your xib and played around with a few things... I can only find two items that would be causing problems.
But first, a couple tips (based on my experiences):
Start by making your window larger than you'll need
with your layout, make it maybe 600 x 600
As you add UI elements add only Vertical and Leading constraints (we can center the Cancel/Copy buttons later
of course, I also mean Horizontal inter-element constraints
For wrapping multi-line labels (i.e. your "Choose how you want..." label), give it a specific Width constraint
285 is a good value for your layout... you can adjust this later as desired
Don't give a Bottom constraint yet
Your xib now looks like this (I left your Cancel button Leading as you had it >= 50):
Now you can tweak your vertical spacing constraints, without worrying about the window frame sizing.
Once you're happy with the layout, you only need one Trailing constraint (on your multi-line label) and one Bottom constraint (on the Cancel button).
Add those constraints, and your window frame should resize itself. If it doesn't, selecting "Update Frames" should fix it.
The two problem items that I mentioned... you have Content Hugging Priority on both "Matte thumbnails..." and "Frame thumbs..." set to 250. Those should both be at their default of 750.
Your Cancel/Copy buttons should horizontally center themselves, with your current constraints. I'd suggest, though, putting them in a StackView, and then constraining that StackView Top to the element above it, and Bottom to the Superview... and thenHorizontally Centered.
Edit
Note: I don't work for Apple... these comments are simply my anecdotal observations.
First, Interface Builder is (as we all know) not the same as run-time output. It's close, but it's certainly not identical.
Second, IB makes a lot of assumptions -- while at the same time, it has no idea what we're going to do next.
A perfect example from iOS development:
If, in IB, I add a UIScrollView to a view without adding any content to it, IB will endlessly complain about Content Size Ambiguity... and won't even position the view to reflect the constraints.
That's because IB doesn't know that I'm going to add subviews (with proper constraints) at run-time. And the only way to get rid of the Red "Error" messages is to add a subview in IB which I would then have to immediately remove at run-time (ugh).
IB also tends to complain / warn about "Fixed width constraints may cause clipping" and, as you're experiencing, "Localization Issue: Trailing constraint is missing."
It doesn't matter if my label is static (text will never change) or if I have the constraints set exactly how I want them. IB is going to try its darnedest to convince me that I need to change things.
This is one of the reasons many people move away from using Storyboards / XIBs. For my job, we don't use any XIBs, and the only Storyboard we use is for the LaunchScreen (iOS).
I might lay things out in IB to start, and to get a good idea of how I want my layout to look, but then I write it all in code for the end result.
Not that I'm saying this approach is better... plenty of times when I think to myself "Hey, I can drag/drop UI elements, add constraints and IBOutlet / IBAction connections, and I'd be done already!"
Again, though, just my personal thoughts on the topic.
In my Xcode 6.2 Swift project I have an issue with Auto-layout (I'm also using size classes) that I'm not able to figure out...
In Main.storyboard I have a view containing, among the others interface elements, a UIButton and a UISegmentedControl that are positioned at the same height on the opposite side of the view.
I'm setting manually all the constraints in Interface Builder (none in code) and my every view is working just fine, except in this case (and this particular issue only occurs when I have a long text).
The button is aligned to the left border of the view and its constraints are:
Leading space to superview == 0
Trailing space to segmented controller >= 8
Top and bottom space to other interface elements == 8
The segmented controller (which has 2 segments) is aligned to the right border of the view and its constraints are:
Trailing space to superview == 0
Leading space to button >= 8
Top and bottom space to other interface elements == 8
The button in the storyboard has a title "Some title", but actually the actual title is always set in code in ViewWillAppear:
myButton.setTitle(aStringThatSometimesIsPrettyLong, forState: .Normal)
The visual result I need to achieve (on every possibile device and orientation) is that the Button title I set in code, while it can be displayed in good length in the interface, should never compromise the size of the segmented control, compressing the labels of the two segments.
So, I want the size of the segmented control to be fixed and I'm willing to accept the fact that the Button title, if long, can be truncated with dots.
Instead, no matter what I try (and I'll explain what I've tried in a moment) when the Button title is very long it is not truncated, instead the segmented control is compressed and therefore its two segments labels are truncated.
So far, I've tried, separately and together, these steps:
Adding a width minimum constraint to the segmented control.
Incremented (in steps, up to 1000) the Content compression resistance of the segmented control while decreasing the correspondent value of the button.
Increased (up to 1000) the Content hugging priority of the button.
I think I can't set a maximum width to the Button, because it can stretch depending on the title set in code and, more important, on what device the app is run on.
My biggest issue is that, no matter what I try, when I run the app I always get the same behavior (button title completely shown, un-truncated, and compressed segmented control). It seems like adding these constraints doesn't change anything, and it never happened to me before with Auto-layout... messing up, a lot, but no change adding constraints, this is new!
Maybe the issue is that the button title is set in ViewWillAppear and not in the Storyboard, but my app wouldn't work properly if I couldn't set its title in code.
Last, but pretty important, I have to admit that, while I've managed so far to get Auto-layout and Size classes working on all devices and orientations for all the (over 10) viewcontrollers of my app, I've actually never written a single line of code for Auto.layout and Size classes: I've done everything in Interface Builder and, if possible, I'd really love to continue this way.
Any suggestion would be really, really appreciated!
Thanks in advance,
Cesare
As #KenThomases pointed out, the constraints I was setting were actually right (actually, it also works with less constraints now that, thanks to his answer, I figured out the issue), but the Segmented control wasn't getting its intrinsicContentSize. Fixed that, now everything's fine.
I have a window I'm setting up with auto layout. There is a view in the middle of the window that contains three controls, and I would like the window to refuse to resize horizontally smaller than the intrinsic size of those three controls.
The outer buttons both have horizontal space constraints to "stick" them to the outside of their superview, and the checkbox in the middle has a horizontal space constraint sticking it to the left side of the "Sync text" button. There is also a >= constraint between the "Sync outline" button and the checkbox, to make sure they don't overlap, but the checkbox prefers to hang to the right. All these constraints have a priority of 1000. The window itself has no minimum size specified.
When I use the "Simulate Document" command in Xcode, everything works as I'd expect, and the window won't let you size it smaller than in the screenshot above. However, when I run my application, the window does allow resizing smaller than that width, so that the buttons start to shrink and eventually the controls overlap each other. I'm not implementing any of the size related window delegate methods, so I don't see any place in the app's code where it might be influencing the resizing.
Any ideas on what could be causing this difference in behavior?
OK, I finally figured out what the heck was going on here. It turns out the problem was that I was implementing the -splitView:constraintMinCoordinate:ofSubviewAt: delegate method (as well as the maxCoordinate one) to restrict the size of the split subviews in the vertical direction. Yes, restricting the vertical resizing of the split view affected the horizontal layout of the buttons.
It appears that what happens is that, if you implement those delegate methods, NSSplitView reverts back to using autoresizing masks to layout the subviews rather than auto layout constraints. Since the view containing those buttons is no longer participating in auto layout, the buttons smush together when you resize the window small. In the simulator, the split view doesn't have a delegate set, so all the auto layout stuff works fine in that environment. Note that merely having the methods implemented is enough to trigger this, even if they just return the proposed coordinates unchanged.
The solution ended up being quite easy, which was to delete the delegate methods and replace it with a vertical constraint on the subview to restrict its size instead.
I have a custom NSView subclass, and I put it in a NSScrollView, and the basics are working fine. It always takes up the full width of its available space, so its constraints take up the whole width of the scroll view. How much vertical space it takes up depends on how much data it has to display, so it can scroll vertically, only. That's all great.
The catch is that if my custom view needs less space than the NSScrollView that it's in, it doesn't expand to fill the visible area. In cases where there's less data than fits in the visible area, I want it to expand downwards -- so that space is available as a drag-and-drop target, among other things.
I've tried changing its "hugging priority". I've tried adding a constraint to keep the bottom below the NSClipView's bottom. I can't come up with any constraint-based solution to fix this (though I haven't ruled out the possibility, either).
I've tried catching the notifications when the NSScrollView changes size, and adjusting the custom view's frame if it's too small, but (presumably because I can only change its current frame, while the layout system does all layout later) I can't seem to make this work, either.
Is there a trick for adding a view to an NSScrollView such that it expands to the bottom of the visible area, whenever it would otherwise be too short? It seems like it should be so simple, and I've done it before in cases where I just call -setFrame on everything manually, but once you move to the autolayout world, that approach stops working.
Without knowing what your constraints and content hugging and content compression actually look like...
Content hugging with at least one edge having >= constraint to superview might do it but you might need to adjust priorities.
You might also need to make sure you've implemented intrinsicContentSize in your custom view class this tells the content hugging and compression what they need to knowfirst.
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