Why does the UIScrollView calculate a massive contentSize with AutoLayout? - uiscrollview

So I've followed all the best practices with respect to having a scrollable content view that works with autolayout (first have one direct subview of UIScrollView, add all the pin constraints, etc), especially when you have a UITextView (i.e. disable scrollingEnabled on the text view)
for example here, or just googling UIScrollView and Autolayout you'll find a lot of tutorials that rehash the same thing.
I've done all this, but am noticing that the scrollView is calculating a contentSize that does not match the contentView's height. I have no idea why this is:
I expect moderators to point out other questions. I looked around and nothing really matched what I'm seeing...

For completeness, it was DonMag's comment that solved it. Unknowingly, the constant was not set to 0, which explains why the contentSize.height WAS 680pt larger than the content.

Related

NSTextField resizable to fit content

I have NSTextField with left, right and top constraints defined (no bottom constraint set). I need NSTextField to grow if content can't fit in it and decrease size if there is unused space left.
Now I have: NSTextField automatically expands with strange behavior if it has multi-line text or too much content, also NSTextField doesn't decrease own's size on window resize.
I haven't found any simple solution written on Swift to solve that problem (I have a lot of such labels with constraints), at iOS everything was working with usual text labels and constraints.
I've created simple project for that question that you can see the problem: [Download Text.zip]
The solutions I've found but not used:
You can try to calculate possible TextField height and set height constraint for it. Problems of that solution:
Possible height calculations are inaccurate, sometimes you calculate incorrect height.
Solutions are written on Objective-C with some complex code.
Run .sizeToFit() on each window resize or text change action. It's not working because .sizeToFit() always compress all text to single line.
Use NSTextView instead of NSTextField. It's good way, but:
I don't need scrolling, editing and other functional of NSTextView. I don't want to call to complex component for simple label.
NSTextView always wants height or bottom constraint, I don't know bottom constraint because content can expand down with new text.
I haven't find full solution to make NSTextView's behavior like I want :)
According to Mac OS X Release Notes (section NSTextField intrinsicContentSize improvements) it’s known bug when height of NSTextField is changed, but width is remained the same. We have two ways to fixing it:
We can specify maximumNumberOfLines to a value that makes sense. That is not good way to us because we don’t know actual number of lines and don’t want to calculate it.
We can set preferredMaxLayoutWidth to a real value. I’ve ended with such code:
Code:
override func viewDidLayout() {
super.viewDidLayout()
textField.preferredMaxLayoutWidth = textField.frame.width
}

UIView `readableContentGuide` in Interface Builder?

The iOS 9 readableContentGuide is a UILayoutGuide (essentially, a thing you can pin constraints to) that all UIViews have. The idea is to keep subviews with text from being too wide on iPad in landscape.
It's easy to configure this in code (v1 is the subview, v is its superview):
NSLayoutConstraint.activateConstraints([
v1.topAnchor.constraintEqualToAnchor(v.readableContentGuide.topAnchor),
v1.bottomAnchor.constraintEqualToAnchor(v.readableContentGuide.bottomAnchor),
v1.rightAnchor.constraintEqualToAnchor(v.readableContentGuide.rightAnchor),
v1.leftAnchor.constraintEqualToAnchor(v.readableContentGuide.leftAnchor)
])
Now then. So far so good. However... In two different WWDC videos, it is claimed quite explicitly that you can configure pinning a subview to its superview's readableContentGuide in Interface Builder.
But they don't explain how you do that.
So my question is: How do you do it?
Pin the subview's edges to the superview's margins as usual.
Now, in the superview's Size inspector, check the Follow Readable Width checkbox:

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

Custom NSView in a NSScrollView that takes up the whole visible space

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.

NSSplitView and autolayout

How should I use auto layout constrains inside NSSplitView subview?
My NSSplitView subview has 3 subview: topPane, tableContainer and bottomPane and I set the constrains like this:
NSDictionary* views = NSDictionaryOfVariableBindings(topPane, tableContainer, bottomPane);
for (NSView* view in [views allValues]) {
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
}
[myView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[topPane(34)][tableContainer][bottomPane(24)]|"
options:0
metrics:nil
views:views]];
[mySplitView addSubview:myView];
And got this in console:
Unable to simultaneously satisfy constraints:
(
"<NSLayoutConstraint:0x7fd6c4b1f770 V:[NSScrollView:0x7fd6c4b234c0]-(0)-[CPane:0x7fd6c4b2fd10]>",
"<NSLayoutConstraint:0x7fd6c4b30910 V:[CPane:0x7fd6c4b2f870(34)]>",
"<NSLayoutConstraint:0x7fd6c4b30770 V:|-(0)-[CPane:0x7fd6c4b2f870] (Names: '|':NSView:0x7fd6c4b22e50 )>",
"<NSLayoutConstraint:0x7fd6c4b212f0 V:[CPane:0x7fd6c4b2fd10]-(0)-| (Names: '|':NSView:0x7fd6c4b22e50 )>",
"<NSLayoutConstraint:0x7fd6c4b2f910 V:[CPane:0x7fd6c4b2f870]-(0)-[NSScrollView:0x7fd6c4b234c0]>",
"<NSLayoutConstraint:0x7fd6c4b21290 V:[CPane:0x7fd6c4b2fd10(24)]>",
"<NSAutoresizingMaskLayoutConstraint:0x7fd6c3630430 h=--& v=--& V:[NSView:0x7fd6c4b22e50(0)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7fd6c4b1f770 V:[NSScrollView:0x7fd6c4b234c0]-(0)-[CPane:0x7fd6c4b2fd10]>
I think <NSAutoresizingMaskLayoutConstraint:0x7fd6c3630430 h=--& v=--& V:[NSView:0x7fd6c4b22e50(0)]> causes this, but I can't reset autoresizing mask, because NSSplitView sets it.
What is best way to use auto layout inside split view? And is there any way to handle min/max size of split view subview with auto layout without NSSplitViewDelegate?
I found out that this error appears if I have toolbar in my window and control split view by any of this delegate methods:
splitView:constrainMinCoordinate:ofSubviewAt:
splitView:constrainMaxCoordinate:ofSubviewAt:
splitView:shouldAdjustSizeOfSubview:
Solution was found in attaching toolbar to window in windowDidLoad.
NSSplitView has been a strange thing since the beginning and it would not surprise me if it'll be gone soon. After trying to get NSSplitView working with AutoLayout for a month now and sinking from one despair attack to another, I finally gave up.
My solution is to not use NSSplitView with AutoLayout at all. So either NSSplitView without Autolayout or Autolayout without NSSplitView: this isn't as complicated as it sounds: just lay out your subviews next to each other and add NSLayoutConstraints as IBOutlets. The constants of these constraints can then be set and changed from the controller in code. With that approach you can set the origin (negative offset to slide it out of the window), the width and the relations to other subviews - plus it's really easy to animate constraints with the view's animator (ever tried to animate a NSSplitView?)
The only thing missing is the mouse drag on the dividers, but this can be implemented with a couple of lines, tracking mouseEvents in your custom "SplitView".
There's an autolayout "splitview" example from Apple (unfortunately only vertical) and I've seen at least one new project on github lately. Though for me, I thought it'd be easier to start over with my custom solution for my app's specific needs, rather than trying to create something very universal (thus making it too complex to handle).
Edit: I now completed my custom splitView that loads its subviews from separate nibs. No constraint issues, no autolayout warnings. Compared to the whole month of trying to get it work with NSSplitView, I have now a working custom splitView based on constraints, easily animatable, created in only one evening. I definitely recommend taking this route!
For anyone who stumbles onto this in the future and is looking for a jump-start into constraint-based NSSplitView replacements, I wrote a small project here that attempts to recreate a portion of NSSplitView's features using Auto Layout:
https://github.com/jwilling/JWSplitView
It's somewhat buggy, but it could be a useful reference to anyone wanting to go this route.
10.8 fixed that problem, see its release notes.
Here is my solution for 10.7 (a custom split view):
https://github.com/benuri/HASplitView.git
You do not want to disable translatesAutoresizingMaskIntoConstraints at all. You shouldn't mess with system views constraints. NSSplitView handles the sizing for the individual views itself and you are essentially trying to rip it's control away. Not to mention, you forgot to account for the splitter.
The correct way to set a minimum or maximum (or constant for that matter) width/height on a splitview is to set those things on the views individually. In particular, if you are doing this in code you will need to use 2 separate calls to constraintsWithVisualFormat, because otherwise the visual format language will create constraints between the views.
You can do all of this in IB just fine. You can even set the priority of each view in the split view, which will cause one or the other view to resize when the window does rather than distributing the resize equally.
As much as I hate to disagree, but Auco's answer should't be voted highest. It is not in any way helpful in solving the problem with an adequate amount of work. In my opinion the NSSplitView was only ever a problem to those who didn't read the documentation well enough.
The actual solution to the problem mentioned here is fairly simple: Auto Layout introduced the new "Holding Priorities API" on NSSplitView. And as the documentation says: Setting lower values to the holding priority of a subview will make him more likely to take width earlier. All of this can be set in IB and programmatically without any despair. The amount of work needed: 20 seconds approx.
I load all by a nib file and setTranslatesAutoresizingMaskIntoConstraints:NO afterwards.
So maybe you should first add by [mySplitView addSubview:myView]; your views and disable afterwards the translation of the autosizing mask to constraints and after this you add your contraint to myView.
EDIT:
Ok it seems I missunderstand the myView.
You have to add the constraint to the subviews and not to the splitview.
[topPane addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[topPane(34)]" options:0 metrics:nil views:views]];
[bottomPane addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[bottomPane(24)]" options:0 metrics:nil views:views]];
You don't have to add edge constraints (the "|" in "V:|[topPane(34)]") because the subviews in NSSplitView are already autoresizing.
This lead to this e.g. for the topPane constraint:
NOTE: ignore the subview content, they are just placeholders
It took me some time to get my autolayout clean of warnings but I did get it handled in IB (several splitviews and subviews).
My layout looks like:
RootView
|--1st NSSplitView (3 vertical subviews)
|----UIView (left)
|----2nd NSSplitView (center & 2 horizontal subviews)
|---UIView (top)
|---3rd NSSplitView (bottom & 3 vertical subviews)
|---UIView (left)
|---UIView (center)
|---UIView (right)
|----UIView (right)
My problem was, that I had 19 Warnings in all my subviews but my layout looked fine and worked how it should be.
After a while I found the cause of my warnings: the constraints of the outer views in my first splitview.
Both views (left and right) had a width-constraint with "width >= 200" and the center view (2nd splitview) had no constraints (because its min-width and max-width where handled by its subviews).
The warnings showed me that autolayout wants to shrink my IB-UI-Layout because the calculated min-widths where smaller than my layout but I didn´t want to shrink it in IB.
I added a fixed constraint "width = 200" to both of the outer subviews of my first splitview and checked "remove at build time".
Now my layout is free of warnings and everything works how it should be.
My conclusion:
I think the problem with autolayout and splitviews is that autolayout can not handle the width-constraints of the subviews. The reason we want to use splitviews is, that we want dynamic width of the views and we want it in both directions, shrink and expans.
So there is no width <= xxx && width >= xxx . Autolayout can only handle one of it and we get warnings in IB. You can fix this problem with a temporary constraint in IB which will removed before runtime.
I hope it makes sense what I wrote but it worked fine in my project.
PS: I could not found any solution until today where I found this thread.. so I guess your posts inspired me :-)
I used this class as a workaround, it's not perfect (the subviews stutter a bit) but it unblocked me. I use this class as the custom class inside each split view pane.
#interface FBSplitPaneView : NSView
#end
#implementation FBSplitPaneView
- (void)setFrame:(NSRect)frame
{
for (NSView *subview in self.subviews) {
subview.frame = self.bounds;
}
[super setFrame:frame];
}
#end

Resources