1
votes

Nesting NSSplitView instances leads to a strange behavior. When dragging the nested splitView's splitter, the parent splitView's splitter can be moved to accomodate a larger child as a side-effect of the dragging.

Here is an URL to a minimalist project which reproduces this problem. Simply drag the right-most splitter towards the right until the left-most splitter starts moving on its own. http://filebin.ca/20ymCpNmtts7/NestedSplitTest.zip

To my understanding, the cause of the problem is that when dragging a splitView's splitter, a new constraint is added binding the right edge of the contentView on the left side of the dragged splitter to the left edge of the NSSplitView itself. This constraint is removed when the drag is complete. While the drag is ongoing and the splitter gets constrained by the minimum widths of other panels, then the total of this temporary constraint's constant value exceeds the width that the pane can resize to and this makes the NSSplitView itself grow larger, which in turn resizes the outermost splitter.

I have tried using a NSSplitViewDelegate to constrain the split position but this delegate method is called after the NSSplitView has already grown because of the temporary constraint. Implementing one of the constrainMinCoordinate or constrainMaxCoordinate delegate methods makes the NSSplitView completely ignore the minimum widths configured on the panels.

Also, I cannot seem to get a proper event when a drag starts or ends, either through notifications or by subclassing NSSplitView. Having a hook on both of these events would allow me to add a temporary constraint to make sure the NSSplitView can't grow outwardly. Even overriding mouseDown: and mouseUp: didn't work because mouseUp: never got called after the drag ended.

Update 1: I have found an explanation for the reason mouseUp: is not called : it is because the dragging of the splitter is implemented using a nested RunLoop that runs in NSEventTrackingRunLoopMode. This is how the mouseDragged: and mouseUp: events get swallowed silently. Here is the relevant documentation : https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/EventOverview/HandlingMouseEvents/HandlingMouseEvents.html#//apple_ref/doc/uid/10000060i-CH6-SW4

1

1 Answers

2
votes

Understanding why mouseUp: is not being called was key to implementing a solution to this problem. Here is a custom NSSplitView subclass that prevents the splitView from growing outwardly when dragging a splitter.

@interface NestableSplitView : NSSplitView

@property(strong) NSLayoutConstraint* temporaryWidthConstraint;

@end

@implementation NestableSplitView

- (void)mouseDown:(NSEvent *)theEvent
{
    if (!self.temporaryWidthConstraint) {
        self.temporaryWidthConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:0];
    }
    self.temporaryWidthConstraint.constant = NSWidth(self.bounds);
    [self addConstraint:self.temporaryWidthConstraint];
    [super mouseDown:theEvent]; // This call is blocking until the drag is finished
    [self removeConstraint:self.temporaryWidthConstraint];
}

@end