Простой индикатор не обновляется

Я реализую приложение Какао, которое представляет собой простой индикатор выполнения, который запускается при нажатии кнопки.

Ситуация такова: я вижу, что анимация - это запуск и остановка, когда я нажимаю кнопку, но индикатор выполнения не обновляет значение.

Я также попробовал решение, упомянутое здесь, но оно не работает:
Как мне обновить индикатор выполнения в Какао во время длинного цикла?

Может кто-нибудь помочь увидеть, где проблема в моем исходном коде?

Вот мой источник.

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);

}

1 ответ

Решение

Обновить:

Пара наблюдений:

  1. Мне кажется, что если вы хотите посмотреть 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];
            });
        }
    });
    
  2. Ты используешь startAnimation а также stopAnimation, но согласно документации каждый из них "ничего не делает для определенного индикатора прогресса", поэтому эти вызовы кажутся неподходящими для этой ситуации.


Мой первоначальный ответ ниже был основан на комментарии в Threads and Your User Interface в Руководстве по программированию Threading, в котором говорится:

Если ваше приложение имеет графический интерфейс пользователя, рекомендуется получать связанные с пользователем события и инициировать обновления интерфейса из основного потока вашего приложения. Такой подход помогает избежать проблем синхронизации, связанных с обработкой пользовательских событий и содержимым окна рисования. Некоторые платформы, такие как Какао, обычно требуют такого поведения, но даже для тех, которые этого не делают, сохранение этого поведения в основном потоке имеет преимущество, заключающееся в упрощении логики управления вашим пользовательским интерфейсом.

Но ответ ниже (неправильный) ответ iOS, поэтому не применим.

Оригинальный ответ:

Ваш for Цикл работает в главном потоке, поэтому обновления пользовательского интерфейса не появятся, пока вы не вернетесь к циклу выполнения. Вы также проходите этот цикл так быстро, что даже если вы правильно отправите его в фоновую очередь, вы не увидите изменения хода выполнения при выполнении итерации цикла.

Итак, выполните цикл во вторичном потоке (например, через GCD или очередь операций), а затем отправьте обновления пользовательского интерфейса обратно в основной поток, который теперь свободен для обновления пользовательского интерфейса. Итак, используя ваш теоретический пример, вы можете сделать что-то вроде:

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
        });
    }
});
Другие вопросы по тегам