9

我已经尝试了我能想到的一切,包括我在 SO 和其他邮件列表上找到的所有建议,但我无法弄清楚如何NSSplitView在自动布局开启时以编程方式折叠带有动画的窗格。

这是我现在所拥有的(为了好玩而用 Swift 编写的),但它以多种方式失败:

@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
    })
}

所需的行为和外观是 Mail.app 的行为和外观,它的动画效果非常好。

我在https://github.com/mdiep/NSSplitViewTest上有一个完整的示例应用程序。

4

8 回答 8

23

目标-C:

[[splitViewItem animator] setCollapse:YES]

迅速:

splitViewItem.animator().collapsed = true

在 Apple 的帮助下:

SplitViewItem对应的子ViewController是否在SplitViewController中折叠。默认为否。这可以使用动画代理设置为折叠或展开动画。使用的确切动画可以通过在 -animations 字典中使用“collapsed”键进行设置来自定义。如果在将其添加到 SplitViewController 之前将其设置为 YES,它将最初折叠,并且 SplitViewController 不会导致视图被加载,直到它被取消折叠。这是符合 KVC/KVO 的,如果值因用户交互而发生变化,则会更新。

于 2014-11-07T05:43:30.873 回答
2

在一些帮助下,我最终能够解决这个问题。我已将我的测试项目转换为可重用的NSSplitView子类:https ://github.com/mdiep/MDPSplitView

于 2014-12-03T16:57:52.470 回答
1
/// 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
        }

    }
于 2019-06-13T01:31:07.687 回答
0

如果您使用自动布局并且想要为视图的尺寸/位置的某些方面设置动画,那么您可能会更幸运地为约束本身设置动画。我很快就开始了,NSSplitView但到目前为止只取得了有限的成功。按下按钮后,我可以进行拆分以展开和折叠,但我最终不得不尝试解决由于干扰约束而引起的大量其他问题。如果您不熟悉它,这里有一个简单的约束动画:

- (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 */

    }];

请记住,您只能为约束设置动画constant,而不能为其设置动画priority

于 2014-11-01T14:36:37.607 回答
0

由于某种原因,动画帧的方法都不适用于我的滚动视图。我没有尝试为约束设置动画。

我最终创建了一个自定义动画来为分隔线位置设置动画。如果有人有兴趣,这是我的解决方案:

动画.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

动画.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

我像这样使用它 - 我有一个 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];
    } 
}
于 2015-03-17T20:10:42.357 回答
0

这是一个不需要任何子类或类别的解决方案,不需要任何子类或类别NSSplitViewController(需要 macOS 10.10+),支持自动布局,动画视图,并且适用于 macOS 10.8+。

正如其他人所建议的那样,解决方案是使用 NSAnimationContext,但诀窍是设置context.allowsImplicitAnimation = YESApple docs)。然后像往常一样设置分隔线位置。

#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];
    }];
}

使用自动布局来约束子视图(宽度、最小/最大尺寸等)。确保在 Interface Builder 中检查“核心动画层”(即,将视图设置为支持层)以查看拆分视图和所有子视图——这是动画转换所必需的。(它仍然可以工作,但没有动画。)

此处提供了完整的工作项目:https ://github.com/demitri/SplitViewAutoLayout 。

于 2019-01-21T17:42:36.213 回答
0

我还要补充一点,因为我花了很长时间才弄清楚这一点,如果你有很多约束来定义你的子视图的布局,那么collapseBehavior = .useConstraints在你的(或项目)上的设置可能会有很大的帮助。NSSplitViewItem在我这样做之前,我的拆分视图动画看起来并不正确。YMMV。

于 2021-01-11T20:24:57.397 回答
0

NSSplitViewItem(即排列的子视图NSSplitView)可以完全折叠,如果它可以达到Zero尺寸(宽度或高度)。因此,我们只需要在动画之前停用适当的约束并允许视图到达Zero维度。在动画之后,我们可以再次激活所需的约束。

请参阅我对 SO 问题的评论如何使用动画展开和折叠 NSSplitView 子视图?.

于 2018-02-04T13:02:35.420 回答