NSProgressIndicator不在图层支持的视图中设置动画

时间:2011-01-11 20:14:57

标签: cocoa macos core-animation appkit

我在图层支持的视图的子视图中有一个条形样式NSProgressIndicator。它的行为有点复杂,但在某些点上,它显示为条形不确定的进度指示器。问题是,当处于这种状态时,它没有动画(即旋转理发杆)。关闭图层支持修复了这个问题,但是这使得其他动画窗口不太流畅,所以我希望能有更好的东西。

这是完整的行为:当设置了脏标志的数量时,它应该作为不确定的动画进度指示器可见;然后在短暂的延迟之后(为了确保用户已完成输入),它会转换为确定的进度指示器,并在执行各种操作时填充;最后,在整个过程结束时,它再次隐藏起来。

为实现这一点,我设置了以下绑定:

  • 隐藏绑定到我的模型的loading属性,并带有NSNegateBoolean值转换器。
  • Is Indeterminate 绑定到我的模型的waitingForInput属性。
  • 绑定到我的模型的currentProgress属性(当waitingForInput为真时为0)。
  • 最大值绑定到我的模型的maximumProgress属性(当waitingForInput为真时为0)。

这主要有效,但有一个例外:当waitingForInputYES时,进度指示器不确定时,进度指示器不会设置动画。

进度指示器不更新的通常原因是程序员使用长时间运行的操作来阻止运行循环,但我不这样做:在有问题的时间段内,运行循环完全打开,只有一个计时器等着火。据我所知,它也不是一些奇怪的模式。该应用程序在此期间接受击键和其他事件,没有任何问题。 (后期阶段,确定的进度指示符填满,由异步NSURLConnection驱动,因此它也不会阻塞。)

我已采取了几个步骤来尝试解决此问题:

  • 我尝试将进度指示器上的 Animate 绑定设置为我的模型的waitingForInput属性,例如 Is Indeterminate 。当waitingForInput上发生更改通知时,这会导致动画急剧更新(waitingForInput恰好在每次输入延迟重新启动时发送KVO通知),但我希望动画要比这更顺畅。< / LI>
  • 我尝试使用KVO观察loadingwaitingForInput的变化。当观察到更改时,它会根据需要调用进度指示器的-startAnimation:-stopAnimation:方法。这些没有明显的效果。
  • 我尝试将进度指示器上的usesThreadedAnimation设置为NO。 (Google上的一篇文章表明,这可能有助于更新图层支持的进度指标上的问题。)这没有明显的效果。我也试过YES,只是为了踢,这被证明是徒劳的。

最后,我也尝试过关闭图层背景。当与Animate绑定结合使用时,这确实解决了问题。但是,它会使其他动画的性能无法接受,所以我宁愿避免这样做。

那么,任何想法,任何人?我真的很感激这个问题的一些帮助!

1 个答案:

答案 0 :(得分:5)

没有任何解决方案也不需要你......  a)摸索NSProgressIndicator
的内部  b)Roll Your Own™。

所以我说你应该提交一个错误。

至少在OS X 10.6.5及更高版本上,只要您将一个不确定进度指示器的wantsLayer属性设置为YES,动画就会立即停止 - 您可以自行检查一个简化的测试应用程序(下面的代码)。

有一个名为animate:的方法(自10.5以来已弃用),您可以在NSProgressIndicator上重复调用,可以帮助您(请参阅Using Indeterminate Progress Indicators)。

修改
从计时器调用animate: 后跟displayIfNeeded 编辑2:,如布伦特指出,这是多余的)仍然有效。 “可能”仅仅意味着我不知道在App Store中是否批准使用已弃用的API,或者这对您是否重要。


示例应用

带有一个控制器的简单Cocoa应用程序:

@interface ProgressTester : NSObject {
        NSProgressIndicator *indicator;
}
@property (nonatomic, assign) IBOutlet NSProgressIndicator *indicator;
@property (nonatomic, assign, getter=isLayered) BOOL layered;

- (IBAction)toggleWantsLayer:(id)sender;
@end

@implementation ProgressTester
@synthesize indicator;
@dynamic layered;

- (BOOL)isLayered
{
        return [indicator wantsLayer];
}
- (void)setLayered:(BOOL)wantsLayer
{
        static NSString *layeredKey = @"layered";
        [self willChangeValueForKey:layeredKey];
        [indicator setWantsLayer:wantsLayer];
        [self didChangeValueForKey:layeredKey];
}

- (void)awakeFromNib
{
        // initialize/synchronize UI state
        [self setLayered:NO];
        [indicator startAnimation:self];
}

-(IBAction)toggleWantsLayer:(id)sender
{
        self.layered = ! self.layered;
}
@end

在NIB中:

  1. 控制器实例
  2. 一个风格不确定的NSProgressIndicator(连接到控制器的indicator插座)
  3. 以控制器为目标,toggleWantsLayer:为操作
  4. 的按钮

    布伦特补充道:

    我使用本答案中的信息编写了一个简单的NSProgressIndicator子类:

    http://www.pastie.org/1465755 http://www.pastie.org/1540277

    请注意,在我的测试中,调用-animate:在没有-displayIfNeeded的情况下正常工作。

    您可以随意使用它。不过,如果您使用它,我很乐意听取您的意见!


    丹尼尔补充道:

    关于pastie子类的几点:

    1. initWithFrame:应拨打initWithFrame:而不是init 编辑3 :已在更新的代码段中修复)。
    2. 不需要保留计时器:
      安排NSTimer导致关联的runloop为retain,并且在计时器为invalidate d
      之前不会将其丢弃(编辑3 :已修复)。
    3. 使用计时器的保留周期有一个强有力的候选者:作为NSTimer retains its target,如果在通过计时器动画时释放指示符,则可能永远不会调用dealloc(我知道它是一个边缘 - 但是......)编辑3 :也照顾过。)。
    4. 我不是完全肯定,但认为awakeFromNib的实现是多余的,因为KVO设置已经发生在initWithFrame: 修改3 :在更新的代码段中澄清。
    5. 那就是说,我个人不想合成animationTimer并处理setter中定时器的失效,以完全摆脱KVO的东西。 (观察self有点偏离我的舒适区。)


      由Anne添加:

      从最新的Pastie link添加代码段以用于存档目的:

      <强> ArchProgressIndicator.h

      //
      //  ArchProgressIndicator.h
      //  Translate2
      //
      //  Created by Brent Royal-Gordon on 1/15/11.
      //  Copyright 2011 Architechies. All rights reserved.
      //
      
      #import <Cocoa/Cocoa.h>
      
      
      @interface ArchProgressIndicator : NSProgressIndicator {
      @private
          NSTimer * animationTimer;
      }
      
      // Just like NSProgressIndicator, but works better in a layer-backed view.
      
      @end
      

      <强> ArchProgressIndicator.m

      //
      //  ArchProgressIndicator.m
      //  Translate2
      //
      //  Created by Brent Royal-Gordon on 1/15/11.
      //  Copyright 2011 Architechies. All rights reserved.
      //
      
      #import "ArchProgressIndicator.h"
      
      @interface ArchProgressIndicator ()
      
      @property (assign) NSTimer * animationTimer;
      
      @end
      
      @implementation ArchProgressIndicator
      
      @synthesize animationTimer;
      
      - (void)addObserver {
          [self addObserver:self forKeyPath:@"animationTimer" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:[ArchProgressIndicator class]];
      }
      
      - (id)initWithFrame:(NSRect)frameRect {
          if ((self = [super initWithFrame:frameRect])) {
              [self addObserver];
          }
      
          return self;
      }
      
      // -initWithFrame: may not be called if created by a nib file
      - (void)awakeFromNib {
          [self addObserver];
      }
      
      // Documentation lists this as the default for -animationDelay
      static const NSTimeInterval ANIMATION_UPDATE_INTERVAL = 5.0/60.0;
      
      - (void)startAnimation:(id)sender {
          [super startAnimation:sender];
      
          if([self layer]) {
              self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:ANIMATION_UPDATE_INTERVAL target:self selector:@selector(animate:) userInfo:nil repeats:YES];
          }
      }
      
      - (void)stopAnimation:(id)sender {
          self.animationTimer = nil;
      
          [super stopAnimation:sender];
      }
      
      - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
          if(context == [ArchProgressIndicator class]) {
              if([keyPath isEqual:@"animationTimer"]) {
                  if([change objectForKey:NSKeyValueChangeOldKey] != [NSNull null] && [change objectForKey:NSKeyValueChangeOldKey] != [change objectForKey:NSKeyValueChangeNewKey]) {
                      [[change objectForKey:NSKeyValueChangeOldKey] invalidate];
                  }
              }
          }
          else {
              return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
          }
      }
      
      - (void)dealloc {
          [self removeObserver:self forKeyPath:@"animationTimer"];
      
          [animationTimer invalidate];
      
          [super dealloc];
      }
      
      @end