Subview that expands with window using NSAutolayout - cocoa

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

Related

How can I make window zooming respect auto layout constraints?

To keep things simple, let's say I have a window containing a single view, which has auto layout constraints binding all 4 sides to the window container view with offset 0. And assume that this view also has a constraint setting its aspect ratio to a constant value. If I resize the window manually, then then window nicely maintains the desired aspect ratio. But if I click the little green zoom widget, then the window fills up the whole screen, regardless of the aspect ratio, with part of the view being above the top of the screen. Is there some way I can make zooming resize the window as big as it can be, without violating auto layout constraints?
I couldn't very well detect this problem in the delegate method windowWillResize:toSize:, because that doesn't tell me which screen it's thinking about putting the window on. I could try to fix the window size in the windowDidResize: delegate method, at which time I do know what screen it's on, but I'm not sure exactly how to do that without reinventing Auto Layout's wheel.
Apparently someone thinks I wasn't explicit enough, so I'll try again. Steps to reproduce:
In Xcode, create a new macOS App project using XIB interface.
Open MainMenu.xib and select the window.
Reshape the window to be approximately square.
Using the Attribute Inspector, set the Full Screen behavior for the window to Auxiliary Window.
Drag an Image View from the library and drop it in the window.
In the Attributes Inspector, set the image view to show the NSComputer image and scale axes independently.
Expand the image view to fill up the window content area.
With the image view selected, click the button to add new layout constraints.
Add 5 constraints, binding the 4 sides to the container, and setting the aspect ratio. (see screen shot)
Build and Run.
Observe that if you resize the window by dragging an edge or corner, the aspect ratio remains fixed.
Click the green zoom widget in the title bar of the window, and observe that the window expands without regard for the aspect ratio constraint, cutting off part of the image.
I just set up a test project exactly as you specified, and when I invoke the window zoom widget, the window expands and retains its aspect ratio i.e. it works as expected. The only thing I can think of that might be causing your issue: maybe your content hugging and content compression resistance priorities are at odds with your constraints? Mind you, I just left them at the default values and it worked fine. Unfortunately Mac/AppKit development (esp. when using IB) is rife with these kind of odd bugs and weird behaviour, probably because Apple has not given it any love in years, so bugs creep in/fester and they are clearly so DONE with developing UI the 'old fashioned way'. (Using SwiftUI to make a Mac app is just as frustrating, in different ways, so I'll stick with what I know). FYI, I used Xcode 13.4.1 to create this test project. Good luck!

Why doesn't my NSWindow resize when I change auto layout constraints?

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.

Why does this simple autolayout collapse in IB?

I have a window with a textview in a scrollview and two buttons. I have added constraints at all sides, leading, trailing, top, and bottom. For the buttons I have fixed width and height, and distance to bottom and edge.
I want it to look something like this:
And this is what Interface Builder keeps giving me:
It also keeps offering to fix ambiguities by adding missing constraints, but actually clicking the button has no effect. No constraints are added. The error (and the offer to fix it) remains.
I've tried added the constraints it is asking for, although I can't see why they would be needed and at least the buttons already have (fixed) width constraints. But I keep getting errors and the window keeps getting shrunk to nothing.
When actually running the program, the window looks fine. But I suspect these autolayout errors have something to do with this problem: NSTextView in magnified NSScrollView breaks on resize
The project is here: https://github.com/angstsmurf/spatterlight/tree/helpviewtest
(The branch helpviewtest is a cut down test case created specifically for this problem.)
You have a Text view embedded in a Clip view embedded in a Scroll view, none of which have an intrinsic content size.
IB has no idea what's going to go on at run-time.
So, while everything looks great when you run this, your code will have supplied enough information to lay things out as desired.
To get it to "look right" in IB (and avoid the errors/warnings), you can give the ScrollView a Placeholder Intrinsic Content Size:

Seeking a simple Mac OS NSTextView example using AutoLayout

After much reading and experimenting, I still cannot get a simple TextView to resize fully in the horizontal direction using Xcode 5.0.2 in Mavericks. It resizes partially as the window is resized, then stops with long lines wrapped around even though my containing NSScrollView continues to resize as expected (it has four default constraints and no horizontal scroller).
Can anyone point me to a simple code/IB+AutoLayout example, preferably just a window containing just an NSTextView dragged in from the IB template library --- one that works? The Apple TextEdit sample code is almost irrelevant for this purpose although it does resize horizontally quite well. Also, there is the clip view for which I can find little information.
Any other tips appreciated.
Thanks.
Answering my own question:
Turns out that my problem had nothing to do with AutoLayout and little to do with NSTextView. It was the textfile I was using to test my code! This file was composed of records with tab-delimited fields.
Turns out that NSTextView comes with a default NSParagraphStyle with predefined tab stops that end at character 56 whereas my test file had tabs beyond that. Therefore, my lines wrapped around at the last defined tab no matter how much I stretched the window.
After changing my search terms, I found what I needed at the following links:
Premature line wrapping in NSTextView when tabs are used
How to have unlimited tab stops in a NSTextView with disabled text wrap
Apologies for wasting bandwidth.
Not sure why such a simple thing does not work in your case, but nevertheless here's what I did in Xcode to get an NSTextView follow window resize:
Create a new project (not document based in my case but it doesn't really make a difference)
Drag a NSTextView from the palette to your window. Align all four edges with the window edges.
Open the "Add constraints" pop-up (second button from the segmented control on the bottom-right part of your IB view.
Each of the four spacing constraints should show a number equal to the distance of your text view from the container window. If you aligned them, this number should be either 0 or -1. Click the down arrow for each of them and select "Use Current Canvas Value". Do it for all four. Make sure no other constraints are selected.
Click on "Add constraints" on the bottom of the panel.
Run your project. Your textview should resize with the window.
Also, as Jay's comment mentions, make sure you do not have any "leftover" constraints in your view. You can check this either by observing Xcode's warnings, or manually by inspecting your view's constraints by going to the Size Inspector tab (4th tab on the Utilities bar).
If you need to have your textview arranged in a more complex layout, it might be worth taking a look at the AutoLayout Guide.

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];

Resources