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

Bre*_*don 4 macos cocoa core-animation appkit

我NSProgressIndicator在层支持视图的子视图中有条形.它的行为有点复杂,但在某些点上,它显示为条形不确定的进度指示器.问题是,在这种状态下,它没有动画(即转动理发杆).关闭图层支持可以解决问题,但这使得其他动画窗口不太顺畅,所以我希望能有更好的东西.

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

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

  • 隐藏绑定到我的模型的loading属性与NSNegateBoolean值变换器.
  • Indeterminate是否绑定到我的模型的waitingForInput属性.
  • 值绑定到我的模型的currentProgress属性(当waitingForInput为真时为0 ).
  • Max Value绑定到我的模型的maximumProgress属性(当waitingForInput为true 时为0 ).

这主要工作,但有一个例外:当waitingForInput为YES,因而进度指示器是不确定的,进度指示器没有动画.

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

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

  • 我已经尝试将进度指示器上的Animate绑定设置为我的模型waitingForInput属性,如Is Indeterminate.这会导致动画在更改通知触发时急剧更新waitingForInput(waitingForInput每次输入延迟重新启动时都会发送KVO通知),但我希望动画更平滑.
  • 我一直在使用志愿观察变化都试过loading和waitingForInput.当观察到变化时,它会根据需要调用进度指示器-startAnimation:和-stopAnimation:方法.这些没有明显的效果.
  • 我已经尝试将usesThreadedAnimation进度指示器设置为NO.(Google上的一篇文章表明,这可能有助于更新图层支持的进度指标上的问题.)这没有明显的效果.我也试过YES,只是为了踢,这被证明同样徒劳无功.

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

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

dan*_*dee 5

没有解决方案不需要你...
a)与内部NSProgressIndicator或
b)滚动你自己的™.

所以我要说你应该提交一个bug.

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

有一个名为animate:(自10.5以来已弃用)的方法,您可以反复调用该方法NSProgressIndicator,这可能会对您有所帮助(请参阅使用不确定进度指示器).

编辑:
致电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
Run Code Online (Sandbox Code Playgroud)

在NIB中:

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

布伦特补充道:

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

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

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

您可以随意使用它.不过,如果您使用它,我很乐意收到您的来信!


丹尼尔补充道:

关于pastie子类的几点:

  1. initWithFrame:应该打电话来initWithFrame:代替init(编辑3:修复了更新的片段).
  2. 不需要保留定时器:
    调度a NSTimer会导致关联的runloop,retain并且在计时器为invalidated 之前不会将其丢弃
    (编辑3:固定).
  3. 使用计时器的保留周期有一个强有力的候选者:当NSTimer保留其目标时,如果通过计时器动画释放指示符,则可能永远不会调用dealloc(我知道它是边缘情况但是......)(编辑3:也照顾).
  4. 我不完全确定,但认为实施awakeFromNib是多余的,因为KVO设置已经发生了initWithFrame:(编辑3:在更新的代码段中澄清).

也就是说,我个人不喜欢animationTimer在setter中合成和处理定时器的失效,以完全摆脱KVO的东西.(观察self有点偏离我的舒适区.)


安妮补充道:

从最新的Pastie 链接添加片段以进行存档:

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)