22

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?

Dmitry
  • 7,300
  • 6
  • 32
  • 55
  • Same problem here. I laid everything out in IB rather than programmatically, but have similar debugging output, including the `NSAutoresizingMaskLayoutConstraint`. – Kristopher Johnson Jul 01 '12 at 17:45
  • 1
    This seems fixed under 10.8 but is broken as you note under 10.7. In 10.8 you can set the minimum heights & widths of the content views of the split view in Xcode (4.5.2 anyway). Cannot do this under 10.7 and apps with this created in 10.8 still don't work right in 10.7 – Dad Jan 19 '13 at 23:55
  • Works in general with 10.8+ but constraints need to be specified between subviews for most views - not supre - or you'll get the *Unable to simultaneously satisfy* error.. – Jay Jan 04 '14 at 10:28

9 Answers9

7

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.

raina77ow
  • 103,633
  • 15
  • 192
  • 229
Al Zonke
  • 532
  • 6
  • 8
  • I had the same issue with a NSTabView, unchecking the `Visible at Launch` checkbox of the toolbar and then making it visible in `windowDidLoad` did get rid of the stupid warnings. Thanks for the hint. – DarkDust May 10 '13 at 07:42
  • In an auto layout-based split view with min/max width constraints I had the same issue. Removing all of these 3 delegate methods resolved it. – Lukáš Kubánek Feb 08 '14 at 09:25
  • 3
    Lots of misinformation on this question. This answer is on the right track, but the toolbar business is a red herring. The issue is documented in the AppKit release notes (https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKitOlderNotes/index.html#10_11SplitView); some of the `NSSplitViewDelegate` methods don't play well with autolayout and should not be used. Instead, set up appropriate constraints on the `NSView`s directly under the `NSSplitView`, and it should work fine without logging. – bhaller Apr 25 '17 at 11:14
4

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!

auco
  • 9,329
  • 4
  • 47
  • 54
  • 1
    @Ben-Uri: I have a little project in my [answer](http://stackoverflow.com/a/13964486/456851) that might be of use to you. – sudo rm -rf Dec 20 '12 at 02:40
  • @Jay - Though this answer has been composed when I was running 10.7. it is still valid. As I stated before - it surely works enough for simple layouts but as soon as you are using more complicated auto-layouts, there are definitely issues with NSSplitView. Just try to add resizing and collapsing views and then good luck trying to animate those. – auco Jan 03 '14 at 17:27
  • @auco - just implemented an Xcode Inspector like view using stock NSSplitView and auto layout exclusively. No issues there, collapsing/expanding/adding subviews animated for free using auto layout.. as I've said, at least with 10.8+ it works like a breeze – Jay Jan 04 '14 at 10:25
  • Not trying to be whiny, but I really wonder about these constant anonymous downvotes (at least four since I composed this answer in 2012). I'd find it more helpful if you would please join the discussion with arguments and experiences and/or add or upvote another solution - which clearly doesn't happen as the number of upvotes on the other answers does not change. The fact that this topic exists and that there are very biased opinions indicates that there are some issues with NSSplitView and Autolayout, at least when compatibility to Mac OS 10.7. matters. – auco Jun 18 '15 at 16:36
4

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.

sudo rm -rf
  • 29,408
  • 19
  • 102
  • 161
3

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

Yoav
  • 5,962
  • 5
  • 39
  • 61
1

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.

David Beck
  • 10,099
  • 5
  • 51
  • 88
  • Be interesting to hear how you propose to do this all in IB with AutoLayout turned on.... – Dad Jan 19 '13 at 22:37
  • actually not possible in 10.7 - the control to set the width constraint on the NSSplitView content view is disabled in Xcode 4.5.2 under 10.7 and if you set it in Xcode on 10.8 and then run the app on 10.7 it'll break that constraint in favor of the NSAutoresizingMaskLayerConstraint in 10.7 and so not work. – Dad Jan 19 '13 at 23:54
0

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:

Screenshout

NOTE: ignore the subview content, they are just placeholders

Stephan
  • 4,263
  • 2
  • 24
  • 33
  • If I set `translatesAutoresizingMaskIntoConstraints` to `NO` for `myView`, split view will not handle it's size - it doesn't work with autolayout. – Dmitry Jul 16 '12 at 11:21
0

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.

Jacque
  • 820
  • 1
  • 9
  • 15
  • 1
    This answer would be more useful if it simply answered the question, rather than showing such contempt for other answerers and questioners. I'll give you +1 for the answer and -1 for the rudeness, so they cancel one another out. – Kristopher Johnson Oct 09 '12 at 17:06
  • 1
    @Jacque: If you had also read the documentation, you would have seen that holding priorities only work on OS X 10.8+. There's no need to be inconsiderate against others when perhaps they were thinking about backwards compatibility and you weren't. – sudo rm -rf Oct 22 '12 at 20:44
  • 1
    @Jacque: You may be right if you're dealing with a really simple layout, such as two textViews as subviews of one splitView. But as soon as you need to deal with complicated layouted subviews, setting up holding priorities won't help much: content in a splitView that uses autolayout creates autolayout errors because it conflicts with autoresizing masks. Animating SplitViews is a pain in the butt, because you need to calculate frames, which is a no-go when using auto-layout. There are really much more problems than you can solve with just holding priorities. – auco Nov 10 '12 at 16:10
0

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 :-)

Rikco
  • 376
  • 5
  • 9
0

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
Mathieu Tozer
  • 278
  • 2
  • 8