I working with swift 4 for macOS and I would like to hide an stack view item with animation.
I tried this:
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)
}
}
}
The result will be:
It works! But I am not happy with the animation style. My wish is:
Like a sidebar or if you have an splitview controller and you will do splitviewItem.animator().isCollapsed = true
this animation of show/hide is perfect. Is this wish possible?
UPDATE
self.stack.arrangedSubviews.last!.animator().frame = NSZeroRect
UPDATE 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)
Have a look at Organize Your User Interface with a Stack View sample code.
I disliked the heightContraint on the view controller and the height calculation in the -viewDidLoad. The code below calculation the size before animating and only adds the contrain while animating.
First you need to set the priority of the contraint to something lower than 1000 (required constraint) for the direction you want the animation. Here the bottom contraint.
While we animate the view in and out, we will add contraint to do so with a priority of 1000 (required constraint).
@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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With