2011-06-11 84 views
12

是否可以对NSSplitView子视图的折叠和展开进行动画制作? (我知道其他类的可用性,但宁愿使用NSSplitView而不是动画。)如何使用动画展开和折叠NSSplitView子视图?

我正在使用方法- (void)setPosition:(CGFloat)position ofDividerAtIndex:(NSInteger)dividerIndex来执行折叠和展开。

回答

13

经过一番尝试,我找到了答案:是的,这是可能的。

下面的代码显示了如何完成。 splitView是NSSplitView,它被垂直划分为mainView(左侧)和inspectorView(右侧)。 inspectorView是崩溃的那个。

- (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; 
} 
+2

注意那些谁在非存储管理环境中使用该代码:了'NSViewAnimation'实例泄漏。 – 2011-06-12 10:28:50

+0

在此解决方案展开后,inspectorView分隔线正在消失。在mainView和inspectorView之间点击后,它变得可见。我怀疑它并没有随意看。你知道如何解决这个问题吗? – maseth 2013-03-19 20:53:59

7

这里有一个简单的方法:

http://www.cocoabuilder.com/archive/cocoa/304317-animating-nssplitpane-position.html

它说创建NSSplitView类别如下,然后用

[[splitView animator] setSplitPosition:pos]; 

动画作品对我来说。

类别:

@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 
1

对于没有的动画帧的方法的一些原因,工作了我的滚动视图。

我最终创建了一个自定义动画来为分隔线位置设置动画。这最终花费的时间比我预期的要少。如果有人有兴趣,这里是我的解决方案:

动画.H:

@interface MySplitViewAnimation : NSAnimation 

@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 

动画.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]; 
    } 
    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 

我使用它像这样 - 我有一个3窗格拆分视图,并且将右窗格移入/移出固定量(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

macOS的解决方案10.11。

要点

  1. NSSplitViewItem.minimumThickness取决于NSSplitViewItem .viewController.view宽度/高度的,如果不设置明确。

  2. NSSplitViewItem .viewController.view宽度/高度取决于明确添加的约束条件。

  3. NSSplitViewItem(即布置NSSplitView子视图)可被完全折叠,如果它可以达到Zero尺寸(宽度或高度)。

所以,我们只需要动画之前停用相应的约束,并允许以达到Zero尺寸。动画之后,我们只需要激活所需的约束。

class SplitViewAnimationsController: ViewController { 

    private lazy var toolbarView = StackView().autolayoutView() 
    private lazy var revealLeftViewButton = Button(title: "Left").autolayoutView() 
    private lazy var changeSplitOrientationButton = Button(title: "Swap").autolayoutView() 
    private lazy var revealRightViewButton = Button(title: "Right").autolayoutView() 

    private lazy var splitViewController = SplitViewController() 

    private lazy var viewControllerLeft = ContentViewController() 
    private lazy var viewControllerRight = ContentViewController() 
    private lazy var splitViewItemLeft = NSSplitViewItem(viewController: viewControllerLeft) 
    private lazy var splitViewItemRight = NSSplitViewItem(viewController: viewControllerRight) 

    private lazy var viewLeftWidth = viewControllerLeft.view.widthAnchor.constraint(greaterThanOrEqualToConstant: 100) 
    private lazy var viewRightWidth = viewControllerRight.view.widthAnchor.constraint(greaterThanOrEqualToConstant: 100) 
    private lazy var viewLeftHeight = viewControllerLeft.view.heightAnchor.constraint(greaterThanOrEqualToConstant: 40) 
    private lazy var viewRightHeight = viewControllerRight.view.heightAnchor.constraint(greaterThanOrEqualToConstant: 40) 
    private lazy var equalHeight = viewControllerLeft.view.heightAnchor.constraint(equalTo: viewControllerRight.view.heightAnchor, multiplier: 1) 
    private lazy var equalWidth = viewControllerLeft.view.widthAnchor.constraint(equalTo: viewControllerRight.view.widthAnchor, multiplier: 1) 

    override func loadView() { 
     super.loadView() 
     splitViewController.addSplitViewItem(splitViewItemLeft) 
     splitViewController.addSplitViewItem(splitViewItemRight) 
     contentView.addSubviews(toolbarView, splitViewController.view) 
     addChildViewController(splitViewController) 

     toolbarView.addArrangedSubviews(revealLeftViewButton, changeSplitOrientationButton, revealRightViewButton) 
    } 

    override func viewDidAppear() { 
     super.viewDidAppear() 
     splitViewController.contentView.setPosition(contentView.bounds.width * 0.5, ofDividerAt: 0) 
    } 

    override func setupDefaults() { 
     setIsVertical(true) 
    } 

    override func setupHandlers() { 
     revealLeftViewButton.setHandler { [weak self] in guard let this = self else { return } 
     self?.revealOrCollapse(this.splitViewItemLeft) 
     } 
     revealRightViewButton.setHandler { [weak self] in guard let this = self else { return } 
     self?.revealOrCollapse(this.splitViewItemRight) 
     } 
     changeSplitOrientationButton.setHandler { [weak self] in guard let this = self else { return } 
     self?.setIsVertical(!this.splitViewController.contentView.isVertical) 
     } 
    } 

    override func setupUI() { 

     splitViewController.view.translatesAutoresizingMaskIntoConstraints = false 
     splitViewController.contentView.dividerStyle = .thin 
     splitViewController.contentView.setDividerThickness(2) 
     splitViewController.contentView.setDividerColor(.green) 

     viewControllerLeft.contentView.backgroundColor = .red 
     viewControllerRight.contentView.backgroundColor = .blue 
     viewControllerLeft.contentView.wantsLayer = true 
     viewControllerRight.contentView.wantsLayer = true 

     splitViewItemLeft.canCollapse = true 
     splitViewItemRight.canCollapse = true 

     toolbarView.distribution = .equalSpacing 
    } 

    override func setupLayout() { 
     var constraints: [NSLayoutConstraint] = [] 

     constraints += LayoutConstraint.Pin.InSuperView.horizontally(toolbarView, splitViewController.view) 
     constraints += [ 
     splitViewController.view.topAnchor.constraint(equalTo: contentView.topAnchor), 
     toolbarView.topAnchor.constraint(equalTo: splitViewController.view.bottomAnchor), 
     toolbarView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) 
     ] 

     constraints += [viewLeftWidth, viewLeftHeight, viewRightWidth, viewRightHeight] 
     constraints += [toolbarView.heightAnchor.constraint(equalToConstant: 48)] 

     NSLayoutConstraint.activate(constraints) 
    } 
} 

extension SplitViewAnimationsController { 

    private enum AnimationType: Int { 
     case noAnimation, `default`, rightDone 
    } 

    private func setIsVertical(_ isVertical: Bool) { 
     splitViewController.contentView.isVertical = isVertical 
     equalHeight.isActive = isVertical 
     equalWidth.isActive = !isVertical 
    } 

    private func revealOrCollapse(_ item: NSSplitViewItem) { 

     let constraintToDeactivate: NSLayoutConstraint 
     if splitViewController.splitView.isVertical { 
     constraintToDeactivate = item.viewController == viewControllerLeft ? viewLeftWidth : viewRightWidth 
     } else { 
     constraintToDeactivate = item.viewController == viewControllerLeft ? viewLeftHeight : viewRightHeight 
     } 

     let animationType: AnimationType = .rightDone 

     switch animationType { 
     case .noAnimation: 
     item.isCollapsed = !item.isCollapsed 
     case .default: 
     item.animator().isCollapsed = !item.isCollapsed 
     case .rightDone: 
     let isCollapsedAnimation = CABasicAnimation() 
     let duration: TimeInterval = 3 // 0.15 
     isCollapsedAnimation.duration = duration 
     item.animations = [NSAnimatablePropertyKey("collapsed"): isCollapsedAnimation] 
     constraintToDeactivate.isActive = false 
     setActionsEnabled(false) 
     NSAnimationContext.runImplicitAnimations(duration: duration, animations: { 
      item.animator().isCollapsed = !item.isCollapsed 
     }, completion: { 
      constraintToDeactivate.isActive = true 
      self.setActionsEnabled(true) 
     }) 
     } 
    } 

    private func setActionsEnabled(_ isEnabled: Bool) { 
     revealLeftViewButton.isEnabled = isEnabled 
     revealRightViewButton.isEnabled = isEnabled 
     changeSplitOrientationButton.isEnabled = isEnabled 
    } 
} 

class ContentViewController: ViewController { 

    override func viewDidLayout() { 
     super.viewDidLayout() 
     print("frame: \(view.frame)") 
    } 
} 

enter image description here