4

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

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

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

  • Hidden通过值转换器绑定到我的模型的loading属性。NSNegateBoolean
  • Is Indeterminate绑定到我的模型的waitingForInput属性。
  • 绑定到我的模型的currentProgress属性(waitingForInput为真时为 0)。
  • Max Value绑定到我的模型的maximumProgress属性(waitingForInput为真时为 0)。

这主要是有效的,但有一个例外:当waitingForInputYES,因此进度指示器是不确定的,进度指示器不动画。

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

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

  • 我尝试将进度指示器上的AnimatewaitingForInput绑定设置为模型的属性,例如Is Indeterminate。这会导致动画在更改通知触发时突然更新waitingForInputwaitingForInput碰巧在每次输入延迟重新启动时发送 KVO 通知),但我希望动画比这更流畅。
  • 我尝试使用 KVO 来观察loading和的变化waitingForInput。当观察到变化时,它会酌情调用进度指示器-startAnimation:-stopAnimation:方法。这些没有明显的效果。
  • 我尝试将usesThreadedAnimation进度指示器设置为NO. (谷歌上的一个热门建议这可能有助于更新层支持的进度指标的问题。)这没有明显的效果。我也试过YES,只是为了踢球,这同样是徒劳的。

最后,我还尝试关闭图层支持。当与 Animate 绑定结合使用时,这确实解决了问题。但是,它会不可接受地降低其他动画的性能,所以我宁愿避免这样做。

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

4

1 回答 1

5

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

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

至少在 OS X 10.6.5 及更高版本上,一旦您将 indetermined-progress-indicator 的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

在NIB中:

  1. 控制器实例
  2. 一个样式不确定的 NSProgressIndicator(连接到indicator控制器的出口)
  3. 以控制器为目标和toggleWantsLayer:动作的按钮

布伦特添加:

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

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

请注意,在我的测试中,-animate:没有-displayIfNeeded.

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


丹尼尔补充:

关于 Pastie 子类的几点说明:

  1. initWithFrame:应该调用 toinitWithFrame:而不是init编辑 3:在更新的片段中修复)。
  2. 不需要保留计时器:
    调度 anNSTimer会导致关联的 runloop直到retain计时器结束才释放它invalidate
    编辑 3:也已修复)。
  3. 计时器的保留周期是一个强有力的候选:作为一个NSTimer保留它的目标,如果指示器在通过计时器动画时被释放,则可能永远不会调用 dealloc(我知道这是一个边缘情况,但是......)编辑3:也照顾)。
  4. 我不完全确定,但认为实现awakeFromNib是多余的,因为 KVO 设置已经发生在initWithFrame:编辑 3:在更新的片段中澄清)。

也就是说,我个人不喜欢animationTimer在设置器中合成和处理定时器的失效,以完全摆脱 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

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
于 2011-01-12T10:52:54.790 回答