我正在实现一个Cocoa应用程序,它只是一个简单的进度条,当我按下按钮时启动。
情况是:当我按下按钮时,我可以看到动画是开始和停止,但进度条不会更新值。
我也曾尝试过这里提到的解决方案,但它不起作用:
How do I update a progress bar in Cocoa during a long running loop?
有人可以帮忙查看源代码中的问题在哪里吗?
这是我的来源。
SimpleProgressBar.m
#import "SimpleProgressBar.h"
@implementation SimpleProgressBar
@synthesize progressBar;
int flag=0;
-(IBAction)startProgressBar:(id)sender{
if(flag ==0){
[self.progressBar startAnimation:sender];
flag=1;
}else{
[self.progressBar stopAnimation:sender];
flag=0;
}
[self.progressBar displayIfNeeded];
[self.progressBar setDoubleValue:10.0];
int i=0;
for(i=0;i<100;i++){
NSLog(@"progr: %f",(double)i);
[self.progressBar setDoubleValue:(double)i];
[self.progressBar setNeedsDisplay:YES];
}
}
@end
SimpleProgressBar.h
#import < Foundation/Foundation.h >
@interface SimpleProgressBar : NSObject{
__weak NSProgressIndicator *progressBar;
}
@property (weak) IBOutlet NSProgressIndicator *progressBar;
-(IBAction)startProgressBar:(id)sender;
@end
非常感谢您提供任何有用的答案。
更新
这是我从解决方案移植而来的,它不起作用:
SimpleProgressBar.m
#import "SimpleProgressBar.h"
@implementation SimpleProgressBar
@synthesize progressBar;
int flag=0;
-(IBAction)startProgressBar:(id)sender{
if(flag ==0){
[self.progressBar startAnimation:sender];
flag=1;
}else{
[self.progressBar stopAnimation:sender];
flag=0;
}
[self.progressBar displayIfNeeded];
[self.progressBar setDoubleValue:0.0];
void(^progressBlock)(void);
progressBlock = ^{
[self.progressBar setDoubleValue:0.0];
int i=0;
for(i=0;i<100;i++){
//double progr = (double) i / (double)100.0;
double progr = (double) i;
NSLog(@"progr: %f",progr);
dispatch_async(dispatch_get_main_queue(),^{
[self.progressBar setDoubleValue:progr];
[self.progressBar setNeedsDisplay:YES];
});
}
};
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue,progressBlock);
}
答案 0 :(得分:9)
更新
有几点意见:
令我感到震惊的是,如果你想看NSProgressIndicator
前进,你需要添加一个sleepForTimeInterval
或者for
循环迭代这么快你就不会看到进度指示器前进,但你只是看到它很快就会进入最终状态。如果你插入sleepForTimeInterval
,你应该看到它进展:
self.progressIndicator.minValue = 0.0;
self.progressIndicator.maxValue = 5.0;
[self.progressIndicator setIndeterminate:NO];
self.progressIndicator.doubleValue = 0.001; // if you want to see it animate the first iteration, you need to start it at some small, non-zero value
for (NSInteger i = 1; i <= self.progressIndicator.maxValue; i++)
{
[NSThread sleepForTimeInterval:1.0];
[self.progressIndicator setDoubleValue:(double)i];
[self.progressIndicator displayIfNeeded];
}
或者,如果您想在后台线程上执行for
循环,并将更新分发回主队列:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSInteger i = 1; i <= self.progressIndicator.maxValue; i++)
{
[NSThread sleepForTimeInterval:1.0];
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressIndicator setDoubleValue:(double)i];
[self.progressIndicator displayIfNeeded];
});
}
});
您正在使用startAnimation
和stopAnimation
,但根据the documentation,这些“对确定的进度指示器没有任何作用”,因此这些调用似乎不适合这种情况
我在下面的原始答案取决于线程编程指南中Threads and Your User Interface中的评论,该评论说:
如果您的应用程序具有图形用户界面,建议您从应用程序的主线程接收与用户相关的事件并启动界面更新。此方法有助于避免与处理用户事件和绘制窗口内容相关的同步问题。某些框架(如Cocoa)通常需要这种行为,但即使对于那些不这样做的框架,将此行为保留在主线程上也具有简化管理用户界面的逻辑的优势。
但下面的答案是(错误的)iOS答案,因此不适用。
原始答案:
您的for
循环正在主线程上运行,因此在您回退到runloop之前,UI更新不会出现。你也经历了这么快的循环,即使你正确地将它调度到后台队列,你也不会在迭代循环时遇到进度视图的变化。
因此,在辅助线程上执行循环(例如,通过GCD或操作队列),然后将UI更新分派回主线程,主线程现在可以自由地进行UI更新。因此,使用您的理论示例,您可以执行以下操作:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 100; i++)
{
[NSThread sleepForTimeInterval:0.1];
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressView setProgress: (CGFloat) (i + 1.0) / 100.0 animated:YES];
});
}
});
请注意,如果您正在做一些足够慢的事情来查看进度视图的更改,那么拥有一个更新进度视图的循环才有意义。在原始示例中,您只是从0循环到99,更新进度视图。但这种情况发生得如此之快,以至于在这种情况下进展视图中没有任何意义。这就是为什么我上面的例子不仅为循环使用后台队列,而且还增加了一点延迟(通过sleepForTimeInterval
)。
让我们考虑一个更现实的进度视图应用程序。例如,假设我有一个urls
个数组NSURL
,表示要从服务器下载的项目。然后我可能会做类似的事情:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < [urls count]; i++)
{
// perform synchronous network request (on main queue, you should only do asynchronous network requests, but on background queue, synchronous is fine, and in this case, needed)
NSError *error = nil;
NSURLResponse *response = nil;
NSURLRequest *request = [NSURLRequest requestWithURL:urls[i]];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:response error:error];
// got it; now update my model and UI on the basis of what I just downloaded
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressView setProgress: (CGFloat) (i + 1.0) / [array count] animated:YES];
// do additional UI/model updates here
});
}
});