【问题标题】:How do I animate fadeIn fadeOut effect in NSTextField when changing its text?更改文本时,如何在 NSTextField 中为fadeIn fadeOut 效果设置动画?
【发布时间】:2013-01-05 18:26:30
【问题描述】:

我正在尝试为NSTextField 编写一个类别,它将添加一个新方法setAnimatedStringValue。这个方法应该很好地淡出当前文本,然后设置新文本,然后淡入。

下面是我的实现:-

- (void) setAnimatedStringValue:(NSString *)aString {

    if ([[self stringValue] isEqualToString:aString]) {
        return;
    }

    NSMutableDictionary *dict = Nil;

    NSViewAnimation *fadeOutAnim;
    dict = [NSDictionary dictionaryWithObjectsAndKeys:self, NSViewAnimationTargetKey,
            NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey, nil];
    fadeOutAnim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:
                                                               dict, nil]];
    [fadeOutAnim setDuration:2];
    [fadeOutAnim setAnimationCurve:NSAnimationEaseOut];
    [fadeOutAnim setAnimationBlockingMode:NSAnimationBlocking];

    NSViewAnimation *fadeInAnim;
    dict = [NSDictionary dictionaryWithObjectsAndKeys:self, NSViewAnimationTargetKey,
            NSViewAnimationFadeInEffect, NSViewAnimationEffectKey, nil];
    fadeInAnim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:
                                                                   dict, nil]];
    [fadeInAnim setDuration:3];
    [fadeInAnim setAnimationCurve:NSAnimationEaseIn];
    [fadeInAnim setAnimationBlockingMode:NSAnimationBlocking];

    [fadeOutAnim startAnimation];

    [self setStringValue:aString];

    [fadeInAnim startAnimation];
}

不用说,但上面的代码根本不起作用。我看到的唯一效果是同一窗口上的进度条闪烁。这可能是因为我在尝试“动画”它时阻塞了主运行循环。

请指出上述代码有什么问题。

补充说明:

setAnimatedStringValue 始终由 NSTimer 调用,该 NSTimer 添加到主 NSRunLoop

【问题讨论】:

    标签: objective-c cocoa osx-lion


    【解决方案1】:

    在发布上一个答案后,我正在四处寻找。我留下了那个答案,因为它与您发布的代码非常对应,并使用NSViewAnimation。然而,我确实提出了一个更简洁的版本,尽管使用 NSAnimationContext 来代替它的版本更难阅读(由于块参数缩进)。这里是:

    #import <QuartzCore/QuartzCore.h>
    
    @interface NSTextField (AnimatedSetString)
    
    - (void) setAnimatedStringValue:(NSString *)aString;
    
    @end
    
    @implementation NSTextField (AnimatedSetString)
    
    - (void) setAnimatedStringValue:(NSString *)aString
    {
        if ([[self stringValue] isEqual: aString])
        {
            return;
        }
    
        [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
            [context setDuration: 1.0];
            [context setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut]];
            [self.animator setAlphaValue: 0.0];
        }
                            completionHandler:^{
            [self setStringValue: aString];
            [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
                [context setDuration: 1.0];
                [context setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn]];
                [self.animator setAlphaValue: 1.0];
            } completionHandler: ^{}];
        }];
    }
    
    @end
    

    注意:要访问此处使用的 CAMediaTimingFunction 类以使用此 API 指定非默认计时函数,您需要在项目中包含 QuartzCore.framework

    同样在GitHub

    【讨论】:

    • +1 昨天晚上,这正是我自己最终修复它的方式。但是,我的代码并没有这么优雅。
    【解决方案2】:

    对于 Swift 3,这里有两个方便的 setText()setAttributedText() 扩展方法,可以从一个文本淡入另一个文本:

    import Cocoa
    
    extension NSTextField {
        func setStringValue(_ newValue: String, animated: Bool = true, interval: TimeInterval = 0.7) {
            guard stringValue != newValue else { return }
            if animated {
                animate(change: { self.stringValue = newValue }, interval: interval)
            } else {
                stringValue = newValue
            }
        }
    
        func setAttributedStringValue(_ newValue: NSAttributedString, animated: Bool = true, interval: TimeInterval = 0.7) {
            guard attributedStringValue != newValue else { return }
            if animated {
                animate(change: { self.attributedStringValue = newValue }, interval: interval)
            }
            else {
                attributedStringValue = newValue
            }
        }
    
        private func animate(change: @escaping () -> Void, interval: TimeInterval) {
            NSAnimationContext.runAnimationGroup({ context in
                context.duration = interval / 2.0
                context.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
                animator().alphaValue = 0.0
            }, completionHandler: {
                change()
                NSAnimationContext.runAnimationGroup({ context in
                    context.duration = interval / 2.0
                    context.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
                    self.animator().alphaValue = 1.0
                }, completionHandler: {})
            })
        }
    }
    

    如下称呼他们:

    var stringTextField: NSTextField
    var attributedStringTextField: NSTextField
    ...
    stringTextField.setStringValue("New Text", animated: true)
    ...
    let attributedString = NSMutableAttributedString(string: "New Attributed Text")
    attributedString.addAttribute(...)
    attributedStringTextField.setAttributedStringValue(attributedString, animated: true)
    

    【讨论】:

      【解决方案3】:

      我会刺伤:

      我在这里发现了几个问题。首先,这整件事被设置为阻塞,所以它将阻塞主线程 5 秒。这将转换为用户作为 SPOD/挂起。您可能希望它是非阻塞的,但它需要一些额外的机器才能实现。

      此外,您正在使用NSAnimationEaseOut 来实现淡出效果,这是由一个已知​​的错误影响的,它会导致动画向后运行。 (谷歌搜索“NSAnimationEaseOut backs”,你可以看到很多人都遇到了这个问题。)我在这个例子的两条曲线上都使用了NSAnimationEaseIn

      我得到了这个用于非阻塞动画的简单示例。我不会说这是 理想 方法(我发布了可以说更好的第二个答案),但它有效,并且有望成为您的起点。这是它的症结所在:

      @interface NSTextField (AnimatedSetString)
      
      - (void) setAnimatedStringValue:(NSString *)aString;
      
      @end
      
      @interface SOTextFieldAnimationDelegate : NSObject <NSAnimationDelegate>
      
      - (id)initForSettingString: (NSString*)newString onTextField: (NSTextField*)tf;
      
      @end
      
      @implementation NSTextField (AnimatedSetString)
      
      - (void) setAnimatedStringValue:(NSString *)aString
      {
          if ([[self stringValue] isEqual: aString])
          {
              return;
          }
      
          [[[SOTextFieldAnimationDelegate alloc] initForSettingString: aString onTextField: self] autorelease];
      }
      
      @end
      
      
      @implementation SOTextFieldAnimationDelegate
      {
          NSString* _newString;
          NSAnimation* _fadeIn;
          NSAnimation* _fadeOut;
          NSTextField* _tf;
      }
      
      - (id)initForSettingString: (NSString*)newString onTextField: (NSTextField*)tf
      {
          if (self = [super init])
          {
              _newString = [newString copy];
              _tf = [tf retain];
      
              [self retain]; // we'll autorelease ourselves when the animations are done.
      
              _fadeOut = [[NSViewAnimation alloc] initWithViewAnimations: @[ (@{
                                                                              NSViewAnimationTargetKey : tf ,
                                                                              NSViewAnimationEffectKey : NSViewAnimationFadeOutEffect})] ];
              [_fadeOut setDuration:2];
              [_fadeOut setAnimationCurve: NSAnimationEaseIn];
              [_fadeOut setAnimationBlockingMode:NSAnimationNonblocking];
              _fadeOut.delegate = self;
      
              _fadeIn = [[NSViewAnimation alloc] initWithViewAnimations: @[ (@{
                                                                              NSViewAnimationTargetKey : tf ,
                                                                              NSViewAnimationEffectKey : NSViewAnimationFadeInEffect})] ];
              [_fadeIn setDuration:3];
              [_fadeIn setAnimationCurve:NSAnimationEaseIn];
              [_fadeIn setAnimationBlockingMode:NSAnimationNonblocking];
      
              [_fadeOut startAnimation];
          }
          return self;
      }
      
      - (void)dealloc
      {
          [_newString release];
          [_tf release];
          [_fadeOut release];
          [_fadeIn release];
          [super dealloc];
      }
      
      - (void)animationDidEnd:(NSAnimation*)animation
      {
          if (_fadeOut == animation)
          {
              _fadeOut.delegate = nil;
              [_fadeOut release];
              _fadeOut = nil;
      
              _tf.hidden = YES;
              [_tf setStringValue: _newString];
      
              _fadeIn.delegate = self;
              [_fadeIn startAnimation];
          }
          else
          {
              _fadeIn.delegate = nil;
              [_fadeIn release];
              _fadeIn = nil;
      
              [self autorelease];
          }
      }
      
      @end
      

      如果有基于块的 API 那就太好了……这样就不必实现这个委托对象了。

      我把整个项目放在GitHub上。

      【讨论】:

      • 哇...我没想到动画曲线可以修改动画属性。我希望它们只会影响财产 w.r.t 的变化率。时间。所以,对于NSAnimationEaseOut,Alpha 值的下降率会随着动画的进行而下降。
      • 是的,这也让我感到困惑,并且似乎违反了文档,但这就是我观察到的。我不确定那里到底发生了什么。
      • 谷歌搜索“NSAnimationEaseOut 向后”会产生许多遇到此问题的人,因此答案似乎是“已知错误”。
      猜你喜欢
      • 2011-04-03
      • 1970-01-01
      • 2019-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多