2011-06-11 7 views
12

¿Es posible animar el colapso y la expansión de las subvistas NSSplitView? (Soy consciente de la disponibilidad de clases alternativas, pero preferiría usar NSSplitView para tener animaciones.)Cómo expandir y contraer las subvistas NSSplitView con animación?

Estoy utilizando el método - (void)setPosition:(CGFloat)position ofDividerAtIndex:(NSInteger)dividerIndex para realizar el colapso y la expansión.

Respuesta

13

Después de intentarlo un poco, encontré la respuesta: sí, es posible.

El siguiente código muestra cómo se puede hacer. El splitView es el NSSplitView que se divide verticalmente en mainView (a la izquierda) y inspectorView (a la derecha). El inspectorView es el que se colapsa.

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

Nota para aquellos que utilizan este código en un contexto no administrado de memoria: el 'NSViewAnimation' casos se están escapando. –

+0

En esta solución después de expandir el divisor inspectorView está desapareciendo. Se vuelve visible después de hacer clic entre mainView e inspectorView. Reconocí que no se mueve con vistas. ¿Sabes cómo resolver este problema? – maseth

7

Aquí es un método más simple:

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

¿Qué dice a crear una categoría en NSSplitView de la siguiente manera, y luego animar con

[[splitView animator] setSplitPosition:pos]; 

funciona para mí.

Categoría:

@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

Por alguna razón ninguno de los métodos de animación de fotogramas trabajó para mi ScrollView.

Terminé creando una animación personalizada para animar la posición del divisor. Esto terminó tomando menos tiempo de lo que esperaba. Si alguien está interesado, aquí está mi solución:

Animación .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 Animación

@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 

lo estoy usando como esto - Tengo una visión divisor 3 panel , y estoy moviendo el panel derecho dentro/fuera por una cantidad fija (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

Solución para macOS 10.11.

puntos principales:

  1. NSSplitViewItem.minimumThickness depende de NSSplitViewItem .viewController.view anchura/altura, si no se establece de manera explícita.

  2. NSSplitViewItem .viewController.view ancho/alto depende de las restricciones explícitamente agregadas.

  3. NSSplitViewItem (es decir ordenadas de subvista de NSSplitView) puede ser completamente plegada, si se puede llegar a Zero dimensión (anchura o altura).

Por lo tanto, sólo tenemos que desactivar restricciones apropiadas antes de la animación y permitir fin de alcanzar Zero dimensión. Después de la animación, solo necesitamos activar las restricciones necesarias.

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

Cuestiones relacionadas