How to minimize the NSSplitView panel with animation when using auto layout?

I tried everything I could come up with, including all the suggestions I found here on SO and other mailing lists, but I can't figure out how to programmatically collapse the NSSplitView area with animation while Auto Layout is on.

Here is what I have right now (written in Swift for fun), but it crashes in several ways:

 @IBAction func toggleSourceList(sender: AnyObject?) { let isOpen = !splitView.isSubviewCollapsed(sourceList.view.superview!) let position = (isOpen ? 0 : self.lastWidth) if isOpen { self.lastWidth = sourceList.view.frame.size.width } NSAnimationContext.runAnimationGroup({ context in context.allowsImplicitAnimation = true context.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) context.duration = self.duration self.splitView.setPosition(position, ofDividerAtIndex: 0) }, completionHandler: { () -> Void in }) } 

The desired behavior and appearance is the Mail.app function, which perfectly animates.

I have a complete sample application available at https://github.com/mdiep/NSSplitViewTest .

+7
source share
7 answers

Objective-C:

 [[splitViewItem animator] setCollapse:YES] 

Swift:

 splitViewItem.animator().collapsed = true 

From the Apples Help:

Regardless of whether the child of the ViewController matches the child of the SplitViewItem, it is minimized to a SplitViewController. The default is NO. This can be set using the animator proxy to spice up the crash or minimize. The exact animation can be set by setting it in the -animation dictionary with the key β€œcrashed”. If this is set to YES until it is added to the SplitViewController, it will first fall off and SplitViewController will not cause it to load until it is opened. This complies with KVC / KVO requirements and will be updated if the value changes due to user interaction.

+17
source

In the end, I was able to figure this out with some help. I converted my test project to a reusable subclass of NSSplitView : https://github.com/mdiep/MDPSplitView

+1
source

If you use Auto-Layout and want to animate any aspect of the size / position of the view, you may have more luck animating the limitations themselves. I quickly went with NSSplitView , but so far I have met with limited success. I can get a split to expand and collapse after clicking the button, but I had to try to crack many other problems caused by tampering with restrictions. In case you are not familiar with this, here is a simple restriction animation:

 - (IBAction)animate:(NSButton *)sender { /* Shrink view to invisible */ NSLayoutConstraint *constraint = self.viewWidthConstraint; [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) { [[NSAnimationContext currentContext] setDuration:0.33]; [[NSAnimationContext currentContext] setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]]; [[constraint animator] setConstant:0]; } completionHandler:^{ /* Do Some clean-up, if required */ }]; 

Remember that you can animate the constraints of constant , you cannot animate its priority .

0
source

For some reason, none of the frame animation methods worked for my scrollview. However, I did not try to revive the restrictions.

In the end, I created a custom animation to animate the position of the separator. If anyone is interested, here is my solution:

Animation .h:

 @interface MySplitViewAnimation : NSAnimation <NSAnimationDelegate> @property (nonatomic, strong) NSSplitView* splitView; @property (nonatomic) NSInteger dividerIndex; @property (nonatomic) float startPosition; @property (nonatomic) float endPosition; @property (nonatomic, strong) void (^completionBlock)(); - (instancetype)initWithSplitView:(NSSplitView*)splitView dividerAtIndex:(NSInteger)dividerIndex from:(float)startPosition to:(float)endPosition completionBlock:(void (^)())completionBlock; @end 

Animation .m

 @implementation MySplitViewAnimation - (instancetype)initWithSplitView:(NSSplitView*)splitView dividerAtIndex:(NSInteger)dividerIndex from:(float)startPosition to:(float)endPosition completionBlock:(void (^)())completionBlock; { if (self = [super init]) { self.splitView = splitView; self.dividerIndex = dividerIndex; self.startPosition = startPosition; self.endPosition = endPosition; self.completionBlock = completionBlock; [self setDuration:0.333333]; [self setAnimationBlockingMode:NSAnimationNonblocking]; [self setAnimationCurve:NSAnimationEaseIn]; [self setFrameRate:30.0]; [self setDelegate:self]; } return self; } - (void)setCurrentProgress:(NSAnimationProgress)progress { [super setCurrentProgress:progress]; float newPosition = self.startPosition + ((self.endPosition - self.startPosition) * progress); [self.splitView setPosition:newPosition ofDividerAtIndex:self.dividerIndex]; if (progress == 1.0) { self.completionBlock(); } } @end 

I use it like this: I have a view divided by 3 panels, and I move the right panel to / from a fixed amount (235).

 - (IBAction)togglePropertiesPane:(id)sender { if (self.rightPane.isHidden) { self.rightPane.hidden = NO; [[[MySplitViewAnimation alloc] initWithSplitView:_splitView dividerAtIndex:1 from:_splitView.frame.size.width to:_splitView.frame.size.width - 235 completionBlock:^{ ; }] startAnimation]; } else { [[[MySplitViewAnimation alloc] initWithSplitView:_splitView dividerAtIndex:1 from:_splitView.frame.size.width - 235 to:_splitView.frame.size.width completionBlock:^{ self.rightPane.hidden = YES; }] startAnimation]; } } 
0
source

NSSplitViewItem (i.e., the NSSplitView stacked view) can be fully minimized if it can reach the Zero size (width or height). Thus, we just need to deactivate the corresponding constraints before the animation and let the view reach the size of Zero . After the animation, we can activate the necessary restrictions again.

See my comment for SO question. How do I expand and collapse NSSplitView routines with animations? .

0
source

This solution, which does not require any subclasses or categories, works without the NSSplitViewController (which requires macOS 10. 10+), supports automatic layout, animates views, and works on macOS 10. 8+.

As others have suggested, the solution is to use NSAnimationContext, but the trick is to set context.allowsImplicitAnimation = YES ( Apple docs ). Then simply set the position of the divider as usual.

 #import <Quartz/Quartz.h> #import <QuartzCore/QuartzCore.h> - (IBAction)toggleLeftPane:(id)sender { [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) { context.allowsImplicitAnimation = YES; context.duration = 0.25; // seconds context.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; if ([self.splitView isSubviewCollapsed:self.leftPane]) { // -> expand [self.splitView setPosition:self.leftPane.frame.size.width ofDividerAtIndex:0]; } else { // <- collapse _lastLeftPaneWidth = self.leftPane.frame.size.width; // optional: remember current width to restore to same size [self.splitView setPosition:0 ofDividerAtIndex:0]; } [self.splitView layoutSubtreeIfNeeded]; }]; } 

Use auto-layout to limit subviews (width, minimum / maximum sizes, etc.). Be sure to check the "Base animation layer" checkbox in Interface Builder (i.e., set views to support the layer) for the split view and all subviews - this is necessary for animating transitions. (This will still work, but without animation.)

The full working draft is available here: https://github.com/demitri/SplitViewAutoLayout .

0
source
 /// Collapse the sidebar func collapsePanel(_ number: Int = 0){ guard number < self.splitViewItems.count else { return } let panel = self.splitViewItems[number] if panel.isCollapsed { panel.animator().isCollapsed = false } else { panel.animator().isCollapsed = true } } 
0
source

Source: https://habr.com/ru/post/977512/


All Articles