使用动画隐藏 NSStackView 的视图项
Hide view item of NSStackView with animation
我在 macOS 上使用 swift 4,我想用动画隐藏堆栈视图项目。
我试过这个:
class ViewController: NSViewController {
@IBOutlet weak var box: NSBox!
@IBOutlet weak var stack: NSStackView!
var x = 0
@IBAction func action(_ sender: Any) {
if x == 0 {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
self.stack.arrangedSubviews.last!.isHidden = true
self.view.layoutSubtreeIfNeeded()
x = 1
}, completionHandler: nil)
} else {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
self.stack.arrangedSubviews.last!.isHidden = false
self.view.layoutSubtreeIfNeeded()
x = 0
}, completionHandler: nil)
}
}
}
结果将是:
有效!
但我对动画风格不满意。
我的愿望是:
- 我按下按钮,红色视图会向右变小
- 我按下按钮,红色视图会向左侧变大。
喜欢侧边栏,或者如果你有一个 splitview 控制器,你会做 splitviewItem.animator().isCollapsed = true
show/hide这个动画很完美。
这个愿望可以实现吗?
更新
self.stack.arrangedSubviews.last!.animator().frame = NSZeroRect
更新 2
self.stack.arrangedSubviews.last!.animator().frame = NSRect(x: self.stack.arrangedSubviews.last!.frame.origin.x, y: self.stack.arrangedSubviews.last!.frame.origin.y, width: 0, height: self.stack.arrangedSubviews.last!.frame.size.height)
我只是创建了一个可以使红色视图动画的简单测试代码,而不是使用按钮,我只是使用了 touchup,请看一下代码:
class ViewController: NSViewController {
let view1 = NSView()
let view2 = NSView()
let view3 = NSView()
var x = 0
var constraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view1.wantsLayer = true
view2.wantsLayer = true
view3.wantsLayer = true
view1.layer?.backgroundColor = NSColor.orange.cgColor
view2.layer?.backgroundColor = NSColor.green.cgColor
view3.layer?.backgroundColor = NSColor.red.cgColor
view1.translatesAutoresizingMaskIntoConstraints = false
view2.translatesAutoresizingMaskIntoConstraints = false
view3.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view1)
self.view.addSubview(view2)
self.view.addSubview(view3)
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view1]|", options: [], metrics: nil, views: ["view1": view1]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view2]|", options: [], metrics: nil, views: ["view2": view2]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view3]|", options: [], metrics: nil, views: ["view3": view3]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view1(==view2)][view2(==view1)][view3]|", options: [], metrics: nil, views: ["view1": view1, "view2": view2, "view3": view3]))
constraint = NSLayoutConstraint(item: view3, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100)
self.view.addConstraint(constraint)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
override func mouseUp(with event: NSEvent) {
if x == 0 {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
constraint.constant = 0
self.view.layoutSubtreeIfNeeded()
x = 1
}, completionHandler: nil)
} else {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
constraint.constant = 100
self.view.layoutSubtreeIfNeeded()
x = 0
}, completionHandler: nil)
}
}
}
查看 Organize Your User Interface with a Stack View 示例代码。
我不喜欢视图控制器上的 heightContraint 和 -viewDidLoad 中的高度计算。下面的代码在动画之前计算大小,并且只在动画时添加约束。
可打破的束缚
首先,您需要将约束的优先级设置为低于 1000 (必需的约束) 以用于您想要的动画方向。这里是底部约束。
当我们为视图 in 和 out 设置动画时,我们将添加约束来做到这一点 with 优先级 1000 (需要约束).
代码
@interface NSStackView (LOAnimation)
- (void)lo_toggleArrangedSubview:(NSView *)view;
@end
@implementation NSStackView (LOAnimation)
- (void)lo_toggleArrangedSubview:(NSView *)view
{
NSAssert([self detachesHiddenViews], @"toggleArrangedSubview requires detachesHiddenViews YES");
NSAssert([[self arrangedSubviews] containsObject:view], @"view not an arrangedSubview");
CGFloat postAnimationHeight = 0;
NSLayoutConstraint *animationContraint = nil;
if (view.hidden) {
view.hidden = NO; // NSStackView will re-add view to its subviews
[self layoutSubtreeIfNeeded]; // calucalte view size
postAnimationHeight = view.bounds.size.height;
animationContraint = [view.heightAnchor constraintEqualToConstant:0];
animationContraint.active = YES;
} else {
[self layoutSubtreeIfNeeded]; // calucalte view size
animationContraint = [view.heightAnchor constraintEqualToConstant:view.bounds.size.height];
animationContraint.active = YES;
}
[self layoutSubtreeIfNeeded]; // layout with animationContraint in place
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
context.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animationContraint.animator.constant = postAnimationHeight;
[self layoutSubtreeIfNeeded];
} completionHandler:^{
view.animator.hidden = (postAnimationHeight == 0);
[view removeConstraint:animationContraint];
}];
}
@end
我在 macOS 上使用 swift 4,我想用动画隐藏堆栈视图项目。
我试过这个:
class ViewController: NSViewController {
@IBOutlet weak var box: NSBox!
@IBOutlet weak var stack: NSStackView!
var x = 0
@IBAction func action(_ sender: Any) {
if x == 0 {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
self.stack.arrangedSubviews.last!.isHidden = true
self.view.layoutSubtreeIfNeeded()
x = 1
}, completionHandler: nil)
} else {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
self.stack.arrangedSubviews.last!.isHidden = false
self.view.layoutSubtreeIfNeeded()
x = 0
}, completionHandler: nil)
}
}
}
结果将是:
有效! 但我对动画风格不满意。 我的愿望是:
- 我按下按钮,红色视图会向右变小
- 我按下按钮,红色视图会向左侧变大。
喜欢侧边栏,或者如果你有一个 splitview 控制器,你会做 splitviewItem.animator().isCollapsed = true
show/hide这个动画很完美。 这个愿望可以实现吗?
更新
self.stack.arrangedSubviews.last!.animator().frame = NSZeroRect
更新 2
self.stack.arrangedSubviews.last!.animator().frame = NSRect(x: self.stack.arrangedSubviews.last!.frame.origin.x, y: self.stack.arrangedSubviews.last!.frame.origin.y, width: 0, height: self.stack.arrangedSubviews.last!.frame.size.height)
我只是创建了一个可以使红色视图动画的简单测试代码,而不是使用按钮,我只是使用了 touchup,请看一下代码:
class ViewController: NSViewController {
let view1 = NSView()
let view2 = NSView()
let view3 = NSView()
var x = 0
var constraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view1.wantsLayer = true
view2.wantsLayer = true
view3.wantsLayer = true
view1.layer?.backgroundColor = NSColor.orange.cgColor
view2.layer?.backgroundColor = NSColor.green.cgColor
view3.layer?.backgroundColor = NSColor.red.cgColor
view1.translatesAutoresizingMaskIntoConstraints = false
view2.translatesAutoresizingMaskIntoConstraints = false
view3.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view1)
self.view.addSubview(view2)
self.view.addSubview(view3)
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view1]|", options: [], metrics: nil, views: ["view1": view1]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view2]|", options: [], metrics: nil, views: ["view2": view2]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view3]|", options: [], metrics: nil, views: ["view3": view3]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view1(==view2)][view2(==view1)][view3]|", options: [], metrics: nil, views: ["view1": view1, "view2": view2, "view3": view3]))
constraint = NSLayoutConstraint(item: view3, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100)
self.view.addConstraint(constraint)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
override func mouseUp(with event: NSEvent) {
if x == 0 {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
constraint.constant = 0
self.view.layoutSubtreeIfNeeded()
x = 1
}, completionHandler: nil)
} else {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
constraint.constant = 100
self.view.layoutSubtreeIfNeeded()
x = 0
}, completionHandler: nil)
}
}
}
查看 Organize Your User Interface with a Stack View 示例代码。
我不喜欢视图控制器上的 heightContraint 和 -viewDidLoad 中的高度计算。下面的代码在动画之前计算大小,并且只在动画时添加约束。
可打破的束缚
首先,您需要将约束的优先级设置为低于 1000 (必需的约束) 以用于您想要的动画方向。这里是底部约束。
当我们为视图 in 和 out 设置动画时,我们将添加约束来做到这一点 with 优先级 1000 (需要约束).
代码
@interface NSStackView (LOAnimation)
- (void)lo_toggleArrangedSubview:(NSView *)view;
@end
@implementation NSStackView (LOAnimation)
- (void)lo_toggleArrangedSubview:(NSView *)view
{
NSAssert([self detachesHiddenViews], @"toggleArrangedSubview requires detachesHiddenViews YES");
NSAssert([[self arrangedSubviews] containsObject:view], @"view not an arrangedSubview");
CGFloat postAnimationHeight = 0;
NSLayoutConstraint *animationContraint = nil;
if (view.hidden) {
view.hidden = NO; // NSStackView will re-add view to its subviews
[self layoutSubtreeIfNeeded]; // calucalte view size
postAnimationHeight = view.bounds.size.height;
animationContraint = [view.heightAnchor constraintEqualToConstant:0];
animationContraint.active = YES;
} else {
[self layoutSubtreeIfNeeded]; // calucalte view size
animationContraint = [view.heightAnchor constraintEqualToConstant:view.bounds.size.height];
animationContraint.active = YES;
}
[self layoutSubtreeIfNeeded]; // layout with animationContraint in place
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
context.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animationContraint.animator.constant = postAnimationHeight;
[self layoutSubtreeIfNeeded];
} completionHandler:^{
view.animator.hidden = (postAnimationHeight == 0);
[view removeConstraint:animationContraint];
}];
}
@end