33

This is as simple as can be so I can't for the life of me find what's wrong, I looked through the documentation as a guide but it still didn't work. I have a view inside a larger view. An IBAction is supposed to fade out the inner view... that's it. Here's what I've got:

NSViewAnimation *theAnim;
NSMutableDictionary *viewDict;

// Create the attributes dictionary for the view.
viewDict = [NSMutableDictionary dictionaryWithCapacity:2];

// Set the target object to be the view.
[viewDict setObject:_innerView forKey:NSViewAnimationTargetKey];

// Set this view to fade out
[viewDict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];

theAnim = [[NSViewAnimation alloc] initWithViewAnimations:@[viewDict]];

// Set some additional attributes for the animation.
[theAnim setDuration:1.0];

// Run the animation.
[theAnim startAnimation];

I checked the viewDict and theAnim with NSLog and neither are nil. I pretty much copied this from an old program I had where this was working, can't find what's wrong now.

I'm using Xcode 5.1.1.

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
Elbimio
  • 1,041
  • 2
  • 10
  • 25

3 Answers3

81

The modern approach is much easier:

[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
    context.duration = 1;
    view.animator.alphaValue = 0;
}
completionHandler:^{
    view.hidden = YES;
    view.alphaValue = 1;
}];

If the view hierarchy is layer-backed, it's actually sufficient to do:

view.animator.hidden = YES;
Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • 7
    To explain the difference, this one is Core Animation, but using the animator proxy for built in animatable properties in AppKit views. The OP is using the old pre-Core Animation stuff, which is also a lot less efficient usually. – uchuugaka Aug 23 '14 at 05:27
  • 1
    i wish i could give you a thousands vote, for this line view.animator.hidden = YES; dude. :) – elk_cloner Mar 21 '19 at 06:14
13

For those looking for a Swift version instead:

NSAnimationContext.runAnimationGroup({ context in
    context.duration = 1
    self.view.animator().alphaValue = 0
}, completionHandler: {
    self.view.isHidden = true
    self.view.alphaValue = 1
})

For layer-backed views, this is enough:

view.animator().isHidden = true

For those already on Swift 5.3 or above, you can leverage the new Multiple Trailing Closures syntax sugar here for an even cleaner version:

NSAnimationContext.runAnimationGroup { context in
    context.duration = 1
    self.view.animator().alphaValue = 0
} completionHandler: {
    self.view.isHidden = true
    self.view.alphaValue = 1
}
Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
  • 1
    Thank you, very helpful! Is there a way to keep the label visible for 2 seconds or so before beginning the fade out? (I am using it as a status notification upon completion of an operation) – Cheetaiean Jun 02 '21 at 16:18
0

The Swift version which successfully working.

// Extension
extension NSViewAnimation {

   public static func make(target: NSView, effect: NSViewAnimation.EffectName) -> NSViewAnimation {
      var animationDict: [NSViewAnimation.Key : Any] = [:]
      animationDict[.target] = target
      animationDict[.effect] = effect
      let viewAnimation = NSViewAnimation(viewAnimations: [animationDict])
      return viewAnimation
   }
}

// Custom subclass of NSView
class MySimpleView: NSView {

   override func draw(_ dirtyRect: NSRect) {
      NSColor.brown.setFill()
      dirtyRect.fill()
   }

   func animateFadeOut() {
      let animation = NSViewAnimation.make(target: self, effect: .fadeOut)
      animation.start()
   }
}

// Usage
let containerView = NSView()
let view = MySimpleView(frame: CGRect(x: 20, y: 20, width: 60, height: 60))
view.autoresizingMask = []
containerView.addSubview(view)

// At some point later
view.animateFadeOut() // Works as expected.
Vlad
  • 6,402
  • 1
  • 60
  • 74