views:

133

answers:

3

I am trying to solve a problem in Objective-C, but I don't think the question is language specific.

I have to do some processing down in a model class that has no notion of UI. However, this processing takes some time and I want to let the user know the status via a progress bar.

My first attempt at this was defining a notion of a progress handler protocol/interface with some methods like

-startOperation;
-updateProgress:(double)currentValue ofMax:(double)maxValue
-endOperation;

This way my UI can implement that the the model need not know details about what goes on other than someone wants progress updates. Currently my UI unhides a progress bar, and updates it, then hides it when done. So far so good.

However, it turns out that sometimes this operation processing is very fast. Such that the UI updates result in a pretty disconcerting flicker as they execute. I don't know if the operation will be fast or slow beforehand.

One idea I had was to force the operation to take at least a certain duration to avoid the UI changes being so jarring to the eye, but this seemed to put knowledge of the UI in the model class, which must be wrong.

This would seem to be a common issue with (hopefully) some known pattern.

How would you address this?

+2  A: 

One thing commonly done is to have your progress bar implementation not show itself right away, and apply some heuristic based on the first couple of updates (or a timeout) to determine whether it needs to show itself at all. That's how the Java ProgressMonitor behaves, for example. (The ProgressMonitor is a nice abstraction that separates the knowledge of progress from its graphical representation).

Once the progress widget is showing, you could repaint it on a leisurely timer, say 10 times per second, rather than reacting to every progress change event with a repaint.

Jonathan Feinberg
+2  A: 

You can use NSTimer to delay the display of your progress bar until your operation had run for a given amount of time, say half a second:

-(void)startOperation {
    // Show the progress bar in 0.5 seconds
    if (!_timer) {
        _timer = [[NSTimer scheduledTimerWithTimeInterval:0.5 
                                                   target:self 
                                                 selector:@selector(showProgressBar:) 
                                                 userInfo:nil 
                                                  repeats:NO] retain];
    }
}

In -endOperation, you cancel the timer and hide progress bar:

-(void)endOperation {
    [_timer invalidate]; // cancel the timer
    [_timer release];
    _timer = nil;

    [self hideProgressBar];
}

If the operation completes in less than 0.5 seconds, the timer is canceled before the progress bar is displayed.

Darren
+3  A: 

Jonathan's and Darren's answers cover your actual problem, but I would add something regarding the question in the title: "How should the model update the UI of its progress?"

The answer, of course, is that it shouldn't. The model shouldn't have to know anything about any protocols for displaying data. There should be one uniform bindings layer taking care about propagating information from the model to the interface. Fortunately, Cocoa already includes such a bindings mechanism: Key-Value Observing.

What you should do is define a property on any model class where the concept of progress makes sense, something like @property (assign) float progress. Then you make sure the class is KVO compliant. Controller code that want to keep track of the progress simply registers to observe this value with something like:

[theObject addObserver:self forKeyPath:@"progress" options:0 context:NULL];

Make sure to read the documentation for the NSKeyValueObserving (KVO) informal protocol. Also, you might want to have a look at Mike Ash's KVO-related notes and code: Key-Value Observing Done Right.

Felixyz
A+++++++ INSIGHTFUL COMMENT WOULD UPVOTE AGAIN!
Jonathan Feinberg
Agreed. This is a great insight. Thanks.
nall