【问题标题】:How to collapse an NSSplitView pane with animation while using Auto Layout?如何在使用自动布局时折叠带有动画的 NSSplitView 窗格?
【发布时间】:2014-10-30 22:50:40
【问题描述】:

我已经尝试了所有我能想到的方法,包括我在 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 有一个完整的示例应用程序。

【问题讨论】:

  • 尝试将-setPosition:ofDividerAtIndex: 发送到拆分视图的animator 代理。
  • @KenThomases 我试过了。没有骰子。

标签: cocoa appkit nssplitview


【解决方案1】:

目标-C:

[[splitViewItem animator] setCollapse:YES]

斯威夫特:

splitViewItem.animator().collapsed = true

来自 Apple 的帮助:

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

【讨论】:

  • 不幸的是,NSSplitViewItem 是 10.10 新 API 的一部分。如果能够使用它,我就不需要问这个问题了。 :)
  • 我明白了。但 NSSplitViewItem 仅适用于 10.10 及更高版本。
  • 我不太明白如何The exact animation used can be customized by setting it in the -animations dictionary。我只想设置一个持续时间,而不是开始和结束值。我的意思是我可以设置一个自定义CABasicAnimation,但我想做的只是修改它的持续时间。另外我在那里设置动画的关键是什么? alpha, frame... ?
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

    • 这假设 splitView 足够大,可以让您从右侧减去 235 个像素。假设您的用户已将窗口大小缩小到 200px 宽:此代码失败是因为它试图将分隔线设置为 (200 - 235 = -35)。拆分视图的框架必须增加。
    【解决方案4】:
    /// 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
            }
    
        }
    

    【讨论】:

      【解决方案5】:

      如果您使用自动布局并且想要为视图的尺寸/位置的某些方面设置动画,那么您可能会更幸运地为约束本身设置动画。我很快就使用了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 设置动画。

      【讨论】:

        【解决方案6】:

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

        请参阅我对 SO 问题的评论 How to expand and collapse NSSplitView subviews with animation?

        【讨论】:

          【解决方案7】:

          这是一个不需要任何子类或类别的解决方案,无需NSSplitViewController(需要 macOS 10.10+)即可工作,支持自动布局,为视图设置动画,并且可在 macOS 10.8+ 上运行。

          正如其他人所建议的,解决方案是使用 NSAnimationContext,但诀窍是设置 context.allowsImplicitAnimation = YES (Apple 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

          【讨论】:

          • 这行得通,除非您尝试在拆分视图窗格折叠时展开拆分视图的最后一个窗格。在这种情况下,Divider 已经位于 splitview 帧的最大 X 或最大 Y 位置。如果拆分视图足够小,以至于剩余的(未折叠的)窗格处于最小尺寸,您不能只设置分隔线 - 拆分视图的框架需要放大。
          • @Bryan - 是的,这是真的。当您想通过调整窗口大小来扩展窗格时,可以在 WWDC 2013 会议“可可动画的最佳实践”,第 213 场会议中找到关于此的精彩讨论:developer.apple.com/videos/play/wwdc2013/213(从 39:20 开始)。跨度>
          【解决方案8】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-06-13
            • 1970-01-01
            相关资源
            最近更新 更多