Подтвердить что ты не робот

Как расширить и свернуть подпрограммы NSSplitView с анимацией?

Возможно ли оживить срыв и расширение подпрограмм NSSplitView? (Я знаю о доступности альтернативных классов, но предпочел бы использовать NSSplitView с анимацией.)

Я использую метод - (void)setPosition:(CGFloat)position ofDividerAtIndex:(NSInteger)dividerIndex для выполнения свертывания и расширения.

4b9b3361

Ответ 1

После нескольких попыток я нашел ответ: да, это возможно.

В приведенном ниже коде показано, как это можно сделать. 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

Вот более простой способ:

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

Ответ 3

По какой-то причине ни один из методов анимации фреймов не работал для моего scrollview.

В итоге я создал пользовательскую анимацию для анимации положения разделителя. В итоге это заняло меньше времени, чем я ожидал. Если кому-то интересно, вот мое решение:

Анимация .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];
    } 
}

Ответ 4

Решение для macOS 10.11.

Основные моменты:

  • NSSplitViewItem.minimumThickness зависит от NSSplitViewItem .viewController.view width/height, если не задано явно.

  • NSSplitViewItem .viewController.view width/height зависит от явно добавленных ограничений.

  • 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)")
   }
}

введите описание изображения здесь