How to expand and collapse NSSplitView subviews with animation?
After some more trying, I found the answer: yes, it's possible.
The code below shows how it can be done. The splitView
is the NSSplitView which is vertically divided into mainView
(on the left) and the inspectorView
(on the right). The inspectorView
is the one that collapses.
- (IBAction)toggleInspector:(id)sender {
if ([self.splitView isSubviewCollapsed:self.inspectorView]) {
// NSSplitView hides the collapsed subview
self.inspectorView.hidden = NO;
NSMutableDictionary *expandMainAnimationDict = [NSMutableDictionary dictionaryWithCapacity:2];
[expandMainAnimationDict setObject:self.mainView forKey:NSViewAnimationTargetKey];
NSRect newMainFrame = self.mainView.frame;
newMainFrame.size.width = self.splitView.frame.size.width-lastInspectorWidth;
[expandMainAnimationDict setObject:[NSValue valueWithRect:newMainFrame] forKey:NSViewAnimationEndFrameKey];
NSMutableDictionary *expandInspectorAnimationDict = [NSMutableDictionary dictionaryWithCapacity:2];
[expandInspectorAnimationDict setObject:self.inspectorView forKey:NSViewAnimationTargetKey];
NSRect newInspectorFrame = self.inspectorView.frame;
newInspectorFrame.size.width = lastInspectorWidth;
newInspectorFrame.origin.x = self.splitView.frame.size.width-lastInspectorWidth;
[expandInspectorAnimationDict setObject:[NSValue valueWithRect:newInspectorFrame] forKey:NSViewAnimationEndFrameKey];
NSViewAnimation *expandAnimation = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:expandMainAnimationDict, expandInspectorAnimationDict, nil]];
[expandAnimation setDuration:0.25f];
[expandAnimation startAnimation];
} else {
// Store last width so we can jump back
lastInspectorWidth = self.inspectorView.frame.size.width;
NSMutableDictionary *collapseMainAnimationDict = [NSMutableDictionary dictionaryWithCapacity:2];
[collapseMainAnimationDict setObject:self.mainView forKey:NSViewAnimationTargetKey];
NSRect newMainFrame = self.mainView.frame;
newMainFrame.size.width = self.splitView.frame.size.width;
[collapseMainAnimationDict setObject:[NSValue valueWithRect:newMainFrame] forKey:NSViewAnimationEndFrameKey];
NSMutableDictionary *collapseInspectorAnimationDict = [NSMutableDictionary dictionaryWithCapacity:2];
[collapseInspectorAnimationDict setObject:self.inspectorView forKey:NSViewAnimationTargetKey];
NSRect newInspectorFrame = self.inspectorView.frame;
newInspectorFrame.size.width = 0.0f;
newInspectorFrame.origin.x = self.splitView.frame.size.width;
[collapseInspectorAnimationDict setObject:[NSValue valueWithRect:newInspectorFrame] forKey:NSViewAnimationEndFrameKey];
NSViewAnimation *collapseAnimation = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:collapseMainAnimationDict, collapseInspectorAnimationDict, nil]];
[collapseAnimation setDuration:0.25f];
[collapseAnimation startAnimation];
}
}
- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview {
BOOL result = NO;
if (splitView == self.splitView && subview == self.inspectorView) {
result = YES;
}
return result;
}
- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex {
BOOL result = NO;
if (splitView == self.splitView && subview == self.inspectorView) {
result = YES;
}
return result;
}
Here's a simpler method:
http://www.cocoabuilder.com/archive/cocoa/304317-animating-nssplitpane-position.html
(Link above dead, new link here.)
Which says create a category on NSSplitView as follows, and then animate with
[[splitView animator] setSplitPosition:pos];
Works for me.
Category:
@implementation NSSplitView (Animation)
+ (id)defaultAnimationForKey:(NSString *)key
{
if ([key isEqualToString:@"splitPosition"])
{
CAAnimation* anim = [CABasicAnimation animation];
anim.duration = 0.3;
return anim;
}
else
{
return [super defaultAnimationForKey:key];
}
}
- (void)setSplitPosition:(CGFloat)position
{
[self setPosition:position ofDividerAtIndex:0];
}
- (CGFloat)splitPosition
{
NSRect frame = [[[self subviews] objectAtIndex:0] frame];
if([self isVertical])
return NSMaxX(frame);
else
return NSMaxY(frame);
}
@end