NSProgressIndicator is not animated in layer-supported mode

I have a NSProgressIndicator style in a frame style in a layer support view. Its behavior is rather complicated, but at certain points it appears as an indefinite indicator of progress in the style of a bar. The problem is that in this state it does not revive (i.e., it rotates the hairdresser's post). Disabling layer support fixes the problem, but it makes other animations in the window less smooth, so I hope for something better.

Here is the full behavior: when a dirty flag is set, it should become visible as an indefinite, animating indicator of progress; then after a short delay (to make sure that the user has finished entering text), he goes to a specific progress indicator and is filled in when performing various operations; and finally, at the end of the whole process, he again hides.

To implement this, I set the following bindings:

  • Hidden is tied to the loading model property with a NSNegateBoolean value NSNegateBoolean .
  • It is undefined, tied to my waitingForInput property.
  • The value is bound to the currentProgress model currentProgress (which is 0 when waitingForInput true).
  • The maximum value is tied to the maximumProgress model maximumProgress (which is 0 when waitingForInput true).

This basically works, but with one exception: when waitingForInput is YES , and therefore the progress indicator is undefined, the progress indicator does not come to life.

The usual reason that the progress indicator is not updated is that the programmer blocks the execution cycle with a long operation, but I do not: during the period under review, the start cycle is fully open, with a single timer awaiting firing. As far as I know, this is not in some weird mode. The application allows keystrokes and other events during this time without any problems. (The later phase of filling out a specific progress indicator is controlled by the asynchronous NSURLConnection , so it does not block.)

I took several steps to fix this problem:

  • I tried to set the Animate binding on the progress bar to a waitingForInput model waitingForInput , such as Is Indeterminate. This causes the animation to update when a change notification is triggered when you turn off waitingForInput ( waitingForInput ) notifications when sending KVO notifications every time you restart input), but I hope for a much smoother animation than that.
  • I tried using KVO to observe changes in both loading and waitingForInput . When a change is observed, it calls up the corresponding progress indicators -startAnimation: and -stopAnimation: They have no visible effect.
  • I tried setting usesThreadedAnimation to a progress indicator of NO . (A Google attempt showed that this could help update problems with progress indicators with level support.) This has no visible effect. I also tried YES , just for kicks, which turned out to be equally useless.

Finally, I also tried disabling layer support. This fixes the issue in conjunction with Animate binding. However, this degrades the performance of other animations is unacceptable, so I would prefer not to.

So, any ideas, anyone? I would really appreciate help in solving this problem!

+4
source share
1 answer

There is no solution that does not require you to ... a) mix up with the internal elements of the NSProgressIndicator or
b) Roll Your Own β„’.

So, I would say that you have to write down the error.

At least on OS X 10.6.5 and higher, as soon as you set the wantsLayer property with an indefinite progress indicator YES , the animation will stop immediately - you can check it for yourself with a reduced test version, application (code below).

The animate: method (deprecated since 10.5) was called, which you could call NSProgressIndicator , which might help you (see Using Undefined Progress Indicators ).

Edit:
animate: call animate: followed by displayIfNeeded ( Change 2: as Brent noted, this is redundant) from the timer still works. β€œMaybe” simply meant that I did not know if the use of the deprecated APIs in the App Store was authorized, or if that was relevant to you at all.


Application example

A simple Cocoa application with one controller:

 @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 

In the NIB:

  • Controller instances
  • one NSProgressIndicator with a standard index (connected to the controller indicator output)
  • button with controller as target and toggleWantsLayer: as action

Added Brent:

I used the information in this answer to write a simple subclass of NSProgressIndicator:

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

Note that in my tests, the -animate: call worked fine without -displayIfNeeded .

Feel free to use it as you wish. I would love to hear from you if you use it, though!


Added by Daniel:

A few points about subclassing on pastie:

  • initWithFrame: should go to initWithFrame: instead of init ( Change 3 : fixed in the updated fragment).
  • The timer does not need to be saved:
    NSTimer calls the associated runloop with retain and does not delete it until the timer invalidate d
    ( Change 3 : fixed).
  • There is a strong candidate for saving a timer loop: since NSTimer maintains its purpose , dealloc will probably never be called if the indicator is released during animation through a timer (I know this is an extreme case, but ...) ( Change 3 : also took care).
  • I'm not quite sure, but I think that the implementation of awakeFromNib redundant, since KVO configuration has already occurred in initWithFrame: ( Change 3 : clarified in the updated fragment).

However, I personally would prefer not to synthesize animationTimer and handle the timer invalidation in the installer to completely get rid of the KVO stuff. (Observing self bit outside of my comfort zone.)


Posted by Ann:

Adding a snippet from the last paste link for archiving purposes:

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 
+5
source

Source: https://habr.com/ru/post/1335155/


All Articles