NSProgressIndicator не анимируется в представлении со слоем - PullRequest
4 голосов
/ 11 января 2011

У меня есть стиль бара NSProgressIndicator в подпредставлении со слоистым представлением.Его поведение довольно сложное, но в определенные моменты оно отображается в виде индикатора неопределенного прогресса в стиле бара.Проблема в том, что когда он находится в этом состоянии, он не оживляет (то есть вращает полюс парикмахера).Отключение поддержки слоя устраняет проблему, но это делает другие анимации в окне менее плавными, поэтому я надеюсь на что-то лучшее.

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

Чтобы реализовать это, я установил следующие привязки:

  • Hidden привязано к свойству loading моей модели с помощью преобразователя значения NSNegateBoolean.
  • Не определено привязано к свойству waitingForInput моей модели.
  • Значение привязано к свойству currentProgress моей модели (которое равно 0, если waitingForInput истинно).
  • Максимальное значение привязано к свойству maximumProgress моей модели(который равен 0, когда waitingForInput истинно).

Это в основном работает, но с одним исключением: когда waitingForInput равно YES, и, таким образом, индикатор прогресса является неопределенным, индикатор прогрессане оживляет.

Обычная причина, по которой индикатор прогресса не обновляется, заключается в том, что программист блокирует цикл выполнения с помощью длительной операции, но я этого не делаю: в течение рассматриваемого периодацикл запуска полностью открыт, с таймером, ожидающим срабатывания.Насколько я знаю, он тоже не в каком-то странном режиме.Приложение принимает нажатия клавиш и другие события в течение этого времени без каких-либо проблем.(Более поздняя фаза с заполнением определенного индикатора прогресса управляется асинхронным NSURLConnection, поэтому он также не блокируется.)

Я предпринял несколько шагов, чтобы попытаться решить эту проблему:

  • Я пытался установить привязку Animate на индикаторе прогресса к свойству waitingForInput моей модели, например Is Indeterminate .Это приводит к резкому обновлению анимации, когда срабатывает уведомление об изменении waitingForInput (waitingForInput отправляет уведомления KVO каждый раз, когда задержка ввода перезапускается), но я надеюсь, что анимация будет намного более плавной, чем эта.* Я пытался использовать KVO для наблюдения изменений как loading, так и waitingForInput.Когда наблюдается изменение, он вызывает методы индикатора прогресса -startAnimation: и -stopAnimation: в зависимости от ситуации.Это не имеет никакого видимого эффекта.
  • Я пытался установить usesThreadedAnimation на индикаторе прогресса на NO.(Хит в Google предположил, что это могло бы помочь с обновлением проблем индикаторов прогресса на уровне слоев.) Это не имеет никакого видимого эффекта.Я также попробовал YES, только для ударов, которые оказались одинаково бесполезными.

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

Итак, есть идеи, кто-нибудь?Буду очень признателен за помощь в решении этой проблемы!

1 Ответ

5 голосов
/ 12 января 2011

Не существует решения, которое бы не требовало от вас ...
а) шарить с внутренностями NSProgressIndicator или
б) Roll Your Own ™.

Так что я бы сказал, что вы должны сообщить об ошибке.

По крайней мере в OS X 10.6.5 и выше, как только вы установите для свойства wantsLayer индикатора неопределенного прогресса значение YES, анимация немедленно остановится - вы можете проверить это сами с помощью уменьшенного теста. приложение (код ниже).

Был метод под названием animate: (устарел с 10.5), который вы могли неоднократно вызывать на NSProgressIndicator, который может помочь вам (см. Использование неопределенных индикаторов прогресса ).

Edit:
Вызов animate: с последующим displayIfNeeded ( Edit 2: , как заметил Брент, это избыточно) из таймера все еще работает. «Может» просто означало, что я не знаю, разрешено ли использование устаревших API в App Store или это вообще важно для вас.


Пример приложения

Простое приложение 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

В СИБ:

  1. Экземпляр контроллера
  2. один NSProgressIndicator с неопределенным стилем (подключен к indicator выходу контроллера)
  3. кнопка с контроллером в качестве цели и toggleWantsLayer: в качестве действия

Добавлено Brent:

Я использовал информацию в этом ответе, чтобы написать простой подкласс NSProgressIndicator:

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

Обратите внимание, что в моих тестах вызов -animate: работал без -displayIfNeeded.

Не стесняйтесь использовать его по своему усмотрению. Я хотел бы услышать от вас, если вы используете это, хотя!


Добавлено от Daniel:

Несколько замечаний о подклассе на pastie:

  1. initWithFrame: должен вызывать initWithFrame: вместо init ( Редактировать 3 : исправлено в обновленном фрагменте).
  2. Таймер не нужно сохранять:
    Планирование NSTimer приводит к тому, что связанный runloop равняется retain и не утилизируется, пока таймер не будет invalidate d
    ( Edit 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
...