1

我正在实现一个 Cocoa 应用程序,它只是一个简单的进度条,当我按下按钮时会启动。

情况是:当我按下按钮时,我可以看到动画是开始和停止,但进度条不会更新值。

我也尝试过这里提到的解决方案,但它不起作用:
How do I update a progress bar in Cocoa during a long running loop?

有人可以帮忙看看我的源代码中的问题在哪里吗?

这是我的来源。

简单进度条.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

简单进度条.h

#import < Foundation/Foundation.h >

@interface SimpleProgressBar : NSObject{

    __weak NSProgressIndicator *progressBar;

}

@property (weak) IBOutlet NSProgressIndicator *progressBar;

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

非常感谢您提供任何有用的答案。

更新:

这是我从解决方案中移植的内容,但它不起作用:

简单进度条.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);

}
4

1 回答 1

9

更新:

几点观察:

  1. 令我震惊的是,如果您想观看进度NSProgressIndicator,则需要添加 asleepForTimeInterval否则for循环迭代得太快以至于您看不到进度指示器的进度,而是会很快看到它以最终状态结束. 如果您 insert 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];
            });
        }
    });
    
  2. 您正在使用startAnimationand stopAnimation,但根据文档,这些“对确定的进度指示器没有任何作用”,因此这些调用似乎不适合这种情况。


下面我的原始答案是基于Threading Programming Guide中的Threads and Your User Interface中的评论,其中说:

如果您的应用程序具有图形用户界面,建议您接收与用户相关的事件并从应用程序的主线程启动界面更新。这种方法有助于避免与处理用户事件和绘制窗口内容相关的同步问题。一些框架,例如 Cocoa,通常需要这种行为,但即使对于那些不需要这种行为的框架,将这种行为保留在主线程上具有简化管理用户界面的逻辑的优势。

但下面的答案是(错误地)iOS 答案,因此不适用。

原答案:

您的for循环在主线程上运行,因此在您返回到运行循环之前不会出现 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
        });
    }
});
于 2013-11-13T03:30:52.167 回答