Как расширить и свернуть подпрограммы NSSplitView с анимацией?
Возможно ли оживить срыв и расширение подпрограмм NSSplitView? (Я знаю о доступности альтернативных классов, но предпочел бы использовать NSSplitView с анимацией.)
Я использую метод - (void)setPosition:(CGFloat)position ofDividerAtIndex:(NSInteger)dividerIndex
для выполнения свертывания и расширения.
Ответы
Ответ 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)")
}
}
![введите описание изображения здесь]()