我在图层支持的视图的子视图中有一个条形样式NSProgressIndicator
。它的行为有点复杂,但在某些点上,它显示为条形不确定的进度指示器。问题是,当处于这种状态时,它没有动画(即旋转理发杆)。关闭图层支持修复了这个问题,但是这使得其他动画窗口不太流畅,所以我希望能有更好的东西。
这是完整的行为:当设置了脏标志的数量时,它应该作为不确定的动画进度指示器可见;然后在短暂的延迟之后(为了确保用户已完成输入),它会转换为确定的进度指示器,并在执行各种操作时填充;最后,在整个过程结束时,它再次隐藏起来。
为实现这一点,我设置了以下绑定:
loading
属性,并带有NSNegateBoolean
值转换器。waitingForInput
属性。currentProgress
属性(当waitingForInput
为真时为0)。maximumProgress
属性(当waitingForInput
为真时为0)。这主要有效,但有一个例外:当waitingForInput
为YES
时,进度指示器不确定时,进度指示器不会设置动画。
进度指示器不更新的通常原因是程序员使用长时间运行的操作来阻止运行循环,但我不这样做:在有问题的时间段内,运行循环完全打开,只有一个计时器等着火。据我所知,它也不是一些奇怪的模式。该应用程序在此期间接受击键和其他事件,没有任何问题。 (后期阶段,确定的进度指示符填满,由异步NSURLConnection
驱动,因此它也不会阻塞。)
我已采取了几个步骤来尝试解决此问题:
waitingForInput
属性,例如 Is Indeterminate 。当waitingForInput
上发生更改通知时,这会导致动画急剧更新(waitingForInput
恰好在每次输入延迟重新启动时发送KVO通知),但我希望动画要比这更顺畅。< / LI>
loading
和waitingForInput
的变化。当观察到更改时,它会根据需要调用进度指示器的-startAnimation:
和-stopAnimation:
方法。这些没有明显的效果。usesThreadedAnimation
设置为NO
。 (Google上的一篇文章表明,这可能有助于更新图层支持的进度指标上的问题。)这没有明显的效果。我也试过YES
,只是为了踢,这被证明是徒劳的。最后,我也尝试过关闭图层背景。当与Animate绑定结合使用时,这确实解决了问题。但是,它会使其他动画的性能无法接受,所以我宁愿避免这样做。
那么,任何想法,任何人?我真的很感激这个问题的一些帮助!
答案 0 :(得分:5)
没有任何解决方案也不需要你......
a)摸索NSProgressIndicator
或
的内部
b)Roll Your Own™。
所以我说你应该提交一个错误。
至少在OS X 10.6.5及更高版本上,只要您将一个不确定进度指示器的wantsLayer
属性设置为YES
,动画就会立即停止 - 您可以自行检查一个简化的测试应用程序(下面的代码)。
有一个名为animate:
的方法(自10.5以来已弃用),您可以在NSProgressIndicator
上重复调用,可以帮助您(请参阅Using Indeterminate Progress Indicators)。
修改强>
从计时器调用animate:
后跟(编辑2:,如布伦特指出,这是多余的)仍然有效。 “可能”仅仅意味着我不知道在App Store中是否批准使用已弃用的API,或者这对您是否重要。displayIfNeeded
带有一个控制器的简单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中:
indicator
插座)toggleWantsLayer:
为操作布伦特补充道:
我使用本答案中的信息编写了一个简单的NSProgressIndicator子类:
http://www.pastie.org/1465755 http://www.pastie.org/1540277
请注意,在我的测试中,调用-animate:
在没有-displayIfNeeded
的情况下正常工作。
您可以随意使用它。不过,如果您使用它,我很乐意听取您的意见!
丹尼尔补充道:
关于pastie子类的几点:
initWithFrame:
应拨打initWithFrame:
而不是init
NSTimer
导致关联的runloop为retain
,并且在计时器为invalidate
d NSTimer
retains its target,如果在通过计时器动画时释放指示符,则可能永远不会调用dealloc(我知道它是一个边缘 - 但是......)awakeFromNib
的实现是多余的,因为KVO设置已经发生在initWithFrame:
那就是说,我个人不想合成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