how to prevent TableView update on key repeat?

  • Hi All,

    I have a master/detail table setup. When I select a row in the master, there are one or two detail tables that need to be updated. There are also a bunch of conditions that need to be checked to know what you can do to the master and the details. I have just finished a lot of optimization but the act of selecting a row is still a pretty heavy operation. Although I've managed to go from 0.3 seconds to 0.02, I'm still behind the key repeat interval.

    I notice that in Mail, if I hold the down arrow and let it repeat, it does *not* update the row until I stop. I don't really know how it does that. My first thought is that I check for a key repeat event in tableViewSelectionDidChange: and skip updating until the user stops. It does not appear to be that easy. Maybe I need to subclass the tableview and handle the keyDown event? That doesn't seem so easy either since I'm using bindings and tableViewSelectionDidChange: is called from NSNotificationCenter via a set of private methods on NSTableView (looks like [NSTableView _doSelectIndexes:byExtendingSelection:indexType:funnelThroughSingleIndexVersion:]).

    Can anyone point me in the right direction on this? How can I prevent tableViewSelectionDidChange: from being called when the user is holding the up or down arrow and trying to scroll rapidly through the table so I only update when they stop?

    Thanks a lot
    Marc
  • On May 3, 2012, at 13:46 , Marc Respass wrote:

    > Can anyone point me in the right direction on this? How can I prevent tableViewSelectionDidChange: from being called when the user is holding the up or down arrow and trying to scroll rapidly through the table so I only update when they stop?

    One approach is to move the heavyweight code out of your selectionDidChange method into a separate method, the invoke this new method via 'performSelector:…afterDelay:0' (or, maybe better still, 'afterDelay:0.1'). In order to prevent the performs from stacking up, you would always invoke 'cancelPreviousPerformRequestsWithTarget:selector:object:' first.

    That is, you always write something like this pattern:

    [cancelPreviousPerformRequestsWithTarget: self selector: @selector (mySelectionDidChange:) object: nil];
    [self performSelector: @selector (mySelectionDidChange) withObject: nil afterDelay: 0.1];

    so you have a queue of 0 or 1 pending selection change actions at any time, and the action is done "soon if not canceled first".

    Other approaches are to use NSOperationQueue to schedule a separate operation, or GCD to schedule a separate block, to the same effect. With a bit more trouble and attention to thread safety, you could arrange for either of these to be done on a background thread, if performance considerations warrant the extra effort.
  • Thanks Quincey (and Alex),

    I ended up finding a solution that almost works perfectly.

    - (void)tableViewSelectionDidChange:(NSNotification *)aNotification;
    {
        NSEvent *event = [NSApp currentEvent];

        if(event != nil && [event type] == NSKeyDown && [event isARepeat])
        {
            return;
        }
        else
        {
    // do my thing
        }
    }

    The problem is that it doesn't update the last row since you get there in a repeat event. I don't love the "do thing with delay," it feels too fragile. I need to update every time the selection changes except when repeating. That's the only condition. If repeating, wait until stop but, naturally, the table view only sends on change.

    What I guess I'm looking for is more like the ability to send tableViewSelectionDidChange: after keyUp. Since the code above is working so well, maybe it's a reasonable idea to subclass NSTableView, note that I'm in a key repeat and send the notification in keyUp if "wasRepeating" is YES. That doesn't sound too bad, not too much code, no threading issues.

    Marc

    El may 3, 2012, a las 5:28 p.m., Quincey Morris escribió:

    > On May 3, 2012, at 13:46 , Marc Respass wrote:
    >
    >> Can anyone point me in the right direction on this? How can I prevent tableViewSelectionDidChange: from being called when the user is holding the up or down arrow and trying to scroll rapidly through the table so I only update when they stop?
    >
    > One approach is to move the heavyweight code out of your selectionDidChange method into a separate method, the invoke this new method via 'performSelector:…afterDelay:0' (or, maybe better still, 'afterDelay:0.1'). In order to prevent the performs from stacking up, you would always invoke 'cancelPreviousPerformRequestsWithTarget:selector:object:' first.
    >
    > That is, you always write something like this pattern:
    >
    > [cancelPreviousPerformRequestsWithTarget: self selector: @selector (mySelectionDidChange:) object: nil];
    > [self performSelector: @selector (mySelectionDidChange) withObject: nil afterDelay: 0.1];
    >
    > so you have a queue of 0 or 1 pending selection change actions at any time, and the action is done "soon if not canceled first".
    >
    > Other approaches are to use NSOperationQueue to schedule a separate operation, or GCD to schedule a separate block, to the same effect. With a bit more trouble and attention to thread safety, you could arrange for either of these to be done on a background thread, if performance considerations warrant the extra effort.
    >
    >
  • On May 3, 2012, at 15:27 , Marc Respass wrote:

    > I ended up finding a solution that almost works perfectly.
    >
    > - (void)tableViewSelectionDidChange:(NSNotification *)aNotification;
    > {
    > NSEvent *event = [NSApp currentEvent];
    >
    > if(event != nil && [event type] == NSKeyDown && [event isARepeat])
    > {
    > return;
    > }
    > else
    > {
    > // do my thing
    > }
    > }

    Well, apologies in advance for being curmudgeonly, but this is a crummy solution.

    You don't know, at the time this method is called, what the current event is. The table view itself might already have deferred sending the notification until some other event occurred (e.g. a NSTimer expiration, or it may even have done its own performSelector…afterDelay). That means that pressing a random key after an arrow key might suppress the detail update entirely.

    On top of that, you're ready to jump in and double your hack by subclassing NSTableView to "fix" the problem the first hack had with keyUp events. Ugh!

    The technique I suggested, of performing a selector and canceling unwanted performs, isn't "fragile". It's a well known (though not immediately obvious) pattern that works well in situations like this. I didn't invent it, I just learned it from someone else on this list. It also has the distinct advantage (in stark contrast to your hack) of being provably correct.
  • El may 3, 2012, a las 6:47 p.m., Quincey Morris escribió:

    > On May 3, 2012, at 15:27 , Marc Respass wrote:
    >
    >> I ended up finding a solution that almost works perfectly.
    >>
    >> - (void)tableViewSelectionDidChange:(NSNotification *)aNotification;
    >> {
    >> NSEvent *event = [NSApp currentEvent];
    >>
    >> if(event != nil && [event type] == NSKeyDown && [event isARepeat])
    >> {
    >> return;
    >> }
    >> else
    >> {
    >> // do my thing
    >> }
    >> }
    >
    > Well, apologies in advance for being curmudgeonly, but this is a crummy solution.

    Not at all. That's why I am asking and responded with something I found. I apologize if I came across ungrateful. I do appreciate the help.

    > You don't know, at the time this method is called, what the current event is. The table view itself might already have deferred sending the notification until some other event occurred (e.g. a NSTimer expiration, or it may even have done its own performSelector…afterDelay). That means that pressing a random key after an arrow key might suppress the detail update entirely.

    Good points.

    > On top of that, you're ready to jump in and double your hack by subclassing NSTableView to "fix" the problem the first hack had with keyUp events. Ugh!

    OK, not a good idea. Unreadying myself :)

    > The technique I suggested, of performing a selector and canceling unwanted performs, isn't "fragile". It's a well known (though not immediately obvious) pattern that works well in situations like this. I didn't invent it, I just learned it from someone else on this list. It also has the distinct advantage (in stark contrast to your hack) of being provably correct.

    It felt to me that performing a selector after a fixed delay would be fragile. I think that I simply didn't understand how running the code via perform selector after delay will solve the problem.

    Let me ask if I have figured it out. User presses down, selection did change is called, I cancel previous performSelector then perform with delay of 0.1 seconds. The key repeat is much faster than 0.1 so selection did change is called before the selector is called and then it is canceled (the cycle then repeats). When the user lifts the key and selection stops, selection did change is *not* called, the selector is scheduled to fire, and it does. If I have that right, then I simply paste the two lines you provided

    [cancelPreviousPerformRequestsWithTarget: self selector: @selector (mySelectionDidChange:) object: nil];
    [self performSelector: @selector (mySelectionDidChange) withObject: nil afterDelay: 0.1];

    and I'm done. Do I have that correct? If so, I apologize that I didn't pay enough attention to get it the first time.

    Thanks a lot
    Marc
  • On May 3, 2012, at 16:07 , Marc Respass wrote:

    > Do I have that correct?

    Yes, you do. :)

    Another way to think about it is this: the cancel suppresses all the selector performs except the last one. (There's *always* a last one because the cancel is done first.) The last one is the only one you really care about or want.
  • El may 3, 2012, a las 7:27 p.m., Quincey Morris escribió:

    > On May 3, 2012, at 16:07 , Marc Respass wrote:
    >
    >> Do I have that correct?
    >
    > Yes, you do. :)
    >
    > Another way to think about it is this: the cancel suppresses all the selector performs except the last one. (There's *always* a last one because the cancel is done first.) The last one is the only one you really care about or want.

    Excellent. Thank you again. [NSEvent keyRepeatInterval] returned 0.833333 so I set the delay to 0.9 and it works perfectly. What was a slow process is now as smooth as can be. It even works correctly if I hold the up/down arrow until I get to the end. The delay to display once I stop the repeat is not noticeable. I just moved my selection code to a method and replaced it with this

        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(selectionDidChange) object:nil];
        [self performSelector:@selector(selectionDidChange) withObject:nil afterDelay:0.09];

    I've been working for days profiling this app to increase the responsiveness of this app and I've made a lot of progress (table view selection change used to take 0.3 seconds, now 0.014) but this is the final straw that will really make a difference.

    Thanks very much!
    Marc
  • On 04/05/2012, at 11:25 AM, Marc Respass wrote:

    > Excellent. Thank you again. [NSEvent keyRepeatInterval] returned 0.833333 so I set the delay to 0.9 and it works perfectly. What was a slow process is now as smooth as can be. It even works correctly if I hold the up/down arrow until I get to the end. The delay to display once I stop the repeat is not noticeable. I just moved my selection code to a method and replaced it with this
    >
    > [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(selectionDidChange) object:nil];
    > [self performSelector:@selector(selectionDidChange) withObject:nil afterDelay:0.09];

    You probably should do this as your last line:

    [self performSelector:<blah> withObject:nil afterDelay:[NSEvent keyRepeatInterval] + 0.1];

    because the key repeat rate is set by the user in the "Keyboard" system preferences and can change.

    --Graham
  • El may 3, 2012, a las 10:38 p.m., Graham Cox escribió:

    >
    > On 04/05/2012, at 11:25 AM, Marc Respass wrote:
    >
    >> Excellent. Thank you again. [NSEvent keyRepeatInterval] returned 0.833333 so I set the delay to 0.9 and it works perfectly. What was a slow process is now as smooth as can be. It even works correctly if I hold the up/down arrow until I get to the end. The delay to display once I stop the repeat is not noticeable. I just moved my selection code to a method and replaced it with this
    >>
    >> [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(selectionDidChange) object:nil];
    >> [self performSelector:@selector(selectionDidChange) withObject:nil afterDelay:0.09];
    >
    >
    > You probably should do this as your last line:
    >
    > [self performSelector:<blah> withObject:nil afterDelay:[NSEvent keyRepeatInterval] + 0.1];
    >
    > because the key repeat rate is set by the user in the "Keyboard" system preferences and can change.

    Thanks Graham. I thought about using [NSEvent keyRepeatInterval] but didn't consider that it's user configurable.

    Marc
  • On May 3, 2012, at 19:38 , Graham Cox wrote:

    > You probably should do this as your last line:
    >
    > [self performSelector:<blah> withObject:nil afterDelay:[NSEvent keyRepeatInterval] + 0.1];
    >
    > because the key repeat rate is set by the user in the "Keyboard" system preferences and can change.

    I see your logic here, but I think this isn't the optimal answer.

    On the one hand, you want the delay to be as short as possible. Let's say the keyboard repeat interval is 0.83 as Marc reported. With the above delay, the user might have to wait more than 0.93 secs to see the detail view *start* to update, even after a single isolated keypress. From this point of view, the best delay is the shortest. From this point of view, the best delay is 0.

    On the other hand, you want the delay to be as long as possible. We're dealing with a situation where the time to update the detail view is greater than the repeat interval. Let's say it takes 1.0 secs to do the update. Once the update (the performed method) starts to execute, then even the next keystroke is delayed for the full 1.0 secs, not just subsequent detail updates. This is a kind of freeze from the user's point of view, hence the pressure to defer it as long as possible.

    [Note: The other scenario to consider here is that the user might mash an arrow key rather than hold it down. The key repeat interval seems irrelevant in this case, another reason not to use it.]

    Statistically (that is, over a wide variety of key presses), the time-alignment of keypresses and updates would [I think] be randomly distributed, so the *expected* completion time after the last keypress in a series would be the total of:

    -- half of the 1.0 sec freeze, or 0.5 secs, resulting from intermediate non-canceled updates occurring at "random" times
    -- the performSelector delay, which must happen before the last update
    -- the full 1.0 sec freeze of the last update.

    That's a really long time -- 1.5 secs with no delay, or 2.33 secs with your suggested repeat-interval-based delay.

    To summarize, therefore, the expected update completion time would be:

    -- after a single keystroke, 'performSelector delay plus update freeze time'

    -- after multiple keystrokes (whether repeated or mashed singly), 'performSelector delay plus 1.5 * update freeze time'

    Since I can't control the update freeze time, I'd *really* want to set the performSelector delay to 0 -- certainly not 0.93.

    My thinking about the delay of 0.1 secs (instead of 0) was do with trying to ensure that the detail update didn't start if another event was about to happen that might get delayed due to the update freeze starting first. On second thoughts, though, I think my reasoning was flawed, so I'm back to thinking a delay of 0 is the best solution.
  • El may 4, 2012, a las 3:19 a.m., Quincey Morris escribió:

    > On May 3, 2012, at 19:38 , Graham Cox wrote:
    >
    >> You probably should do this as your last line:
    >>
    >> [self performSelector:<blah> withObject:nil afterDelay:[NSEvent keyRepeatInterval] + 0.1];
    >>
    >> because the key repeat rate is set by the user in the "Keyboard" system preferences and can change.
    >
    > I see your logic here, but I think this isn't the optimal answer.
    >
    > On the one hand, you want the delay to be as short as possible. Let's say the keyboard repeat interval is 0.83 as Marc reported. With the above delay, the user might have to wait more than 0.93 secs to see the detail view *start* to update, even after a single isolated keypress. From this point of view, the best delay is the shortest. From this point of view, the best delay is 0.
    >
    > On the other hand, you want the delay to be as long as possible. We're dealing with a situation where the time to update the detail view is greater than the repeat interval. Let's say it takes 1.0 secs to do the update. Once the update (the performed method) starts to execute, then even the next keystroke is delayed for the full 1.0 secs, not just subsequent detail updates. This is a kind of freeze from the user's point of view, hence the pressure to defer it as long as possible.
    >
    > [Note: The other scenario to consider here is that the user might mash an arrow key rather than hold it down. The key repeat interval seems irrelevant in this case, another reason not to use it.]
    >
    > Statistically (that is, over a wide variety of key presses), the time-alignment of keypresses and updates would [I think] be randomly distributed, so the *expected* completion time after the last keypress in a series would be the total of:
    >
    > -- half of the 1.0 sec freeze, or 0.5 secs, resulting from intermediate non-canceled updates occurring at "random" times
    > -- the performSelector delay, which must happen before the last update
    > -- the full 1.0 sec freeze of the last update.
    >
    > That's a really long time -- 1.5 secs with no delay, or 2.33 secs with your suggested repeat-interval-based delay.
    >
    > To summarize, therefore, the expected update completion time would be:
    >
    > -- after a single keystroke, 'performSelector delay plus update freeze time'
    >
    > -- after multiple keystrokes (whether repeated or mashed singly), 'performSelector delay plus 1.5 * update freeze time'
    >
    > Since I can't control the update freeze time, I'd *really* want to set the performSelector delay to 0 -- certainly not 0.93.
    >
    >
    > My thinking about the delay of 0.1 secs (instead of 0) was do with trying to ensure that the detail update didn't start if another event was about to happen that might get delayed due to the update freeze starting first. On second thoughts, though, I think my reasoning was flawed, so I'm back to thinking a delay of 0 is the best solution.

    When I set the delay to zero, there is no delay. My selector is called immediately as if I didn't change anything. It seems that the selector must be set to fire just after the key repeat because tableViewSelectionDidChange: is going to be called at the keyRepeatInterval. By setting the interval to [NSEvent keyRepeatInterval] + 0.01; everything works fabulously if your key repeat rate is high because the selection change won't fire until the interval no matter what. When the repeat interval is very slow, the selection changes very slowly no matter how it changes (click, type to select, repeatedly hitting up/down).

    I added this to determine what the delay interval should be

        NSTimeInterval delayInterval = 0.0;
        NSEvent *event = [NSApp currentEvent];

        if(event != nil && [event type] == NSKeyDown && [event isARepeat])
        {
            delayInterval = [NSEvent keyRepeatInterval] + 0.01;
        }

        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(selectionDidChange) object:nil];
        [self performSelector:@selector(selectionDidChange) withObject:nil afterDelay:delayInterval];

    The objection to that was that I don't know what the current event is. If that is true, when [NSApp currentEvent] is useful? It seems that the current event will be the one that caused tableViewSelectionDidChange: to be called.

    In my limited testing with different key repeat rates, everything works fine. I'm using a standard NSTableView. If my key repeat is very slow, then selection change is slow and updates wait until I lift the key. Type to select works fine. Clicking works fine. Repeatedly hitting up/down works fine. I don't see any problem with this but I've already not seen things so thoughts on this solution are appreciated.

    Thanks again
    Marc
  • On May 4, 2012, at 08:25 , Marc Respass wrote:

    > When I set the delay to zero, there is no delay. My selector is called immediately as if I didn't change anything.

    Not exactly. Consider what happens when you mash the arrow key. Thus, there might be multiple (normal) key events queued. The selector won't actually be performed until they're *all* dequeued from the main event queue (because, presumably, any queued perform is further down the queue).

    At that point, no subsequent key events can be dequeued until the update has run. IOW, there's a slight hitch every time the event queue empties of key events. That's *much* better than the original (non-performSelector) approach, because at least some of the updates are suppressed.

    So a delay of 0 helps key-mashers (and doesn't penalize other methods of selection change, such a click). It doesn't help the key-repeat case very much. Trying to solve the repeat case like this is a neat idea:

    > I added this to determine what the delay interval should be
    >
    > NSTimeInterval delayInterval = 0.0;
    > NSEvent *event = [NSApp currentEvent];
    >
    > if(event != nil && [event type] == NSKeyDown && [event isARepeat])
    > {
    > delayInterval = [NSEvent keyRepeatInterval] + 0.01;
    > }
    >
    > [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(selectionDidChange) object:nil];
    > [self performSelector:@selector(selectionDidChange) withObject:nil afterDelay:delayInterval];

    but it still has the drawback that there's an additional '[NSEvent keyRepeatInterval] + 0.01 + timeToUpdateDetailView' delay after the last one, during the latter part of which the user is frozen out of the UI.

    Let's say, though, that you wanted to pursue the approach that the above code represents. In that case, I probably *would* subclass the table view, and override keyDown: and keyUp:. You could simply set a flag at key down and clear it at key up, and use that information to control the way the performSelectors are timed. (It's still something of a hack, but at least it keeps the NSEvent details out of your selectionDidChange code.)

    However, once you get to this level of complexity, then it's probably worthwhile to spend your effort on moving the time-consuming part of the update process into a background thread instead. That would solve the problem for realz, letting keys repeat at their proper rate and letting updates happen as soon as they're ready, without freezes and artificial delays.

    > The objection to that was that I don't know what the current event is. If that is true, when [NSApp currentEvent] is useful? It seems that the current event will be the one that caused tableViewSelectionDidChange: to be called.

    It's useful when you know that you dequeued the last event and that you haven't dequeued another one since. In this case, you know that the table view dequeued a key event, but you don't know what it did after that. It *might* have created a background async GCD block to do the selection pre-processing, then executed a completion handler on the main thread to send the notification that triggered your method. Or, it *might* have looked ahead in the main event queue and dequeued some event.

    Even if you determined empirically that [NSApp currentEvent] gives you the key event, you can't be sure it will continue to do so in future frameworks versions.
  • El may 4, 2012, a las 1:02 p.m., Quincey Morris escribió:

    > On May 4, 2012, at 08:25 , Marc Respass wrote:
    >
    >> When I set the delay to zero, there is no delay. My selector is called immediately as if I didn't change anything.
    >
    >
    > Not exactly. Consider what happens when you mash the arrow key. Thus, there might be multiple (normal) key events queued. The selector won't actually be performed until they're *all* dequeued from the main event queue (because, presumably, any queued perform is further down the queue).
    >
    > At that point, no subsequent key events can be dequeued until the update has run. IOW, there's a slight hitch every time the event queue empties of key events. That's *much* better than the original (non-performSelector) approach, because at least some of the updates are suppressed.
    >
    > So a delay of 0 helps key-mashers (and doesn't penalize other methods of selection change, such a click). It doesn't help the key-repeat case very much. Trying to solve the repeat case like this is a neat idea:
    >
    >> I added this to determine what the delay interval should be
    >>
    >> NSTimeInterval delayInterval = 0.0;
    >> NSEvent *event = [NSApp currentEvent];
    >>
    >> if(event != nil && [event type] == NSKeyDown && [event isARepeat])
    >> {
    >> delayInterval = [NSEvent keyRepeatInterval] + 0.01;
    >> }
    >>
    >> [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(selectionDidChange) object:nil];
    >> [self performSelector:@selector(selectionDidChange) withObject:nil afterDelay:delayInterval];
    >
    > but it still has the drawback that there's an additional '[NSEvent keyRepeatInterval] + 0.01 + timeToUpdateDetailView' delay after the last one, during the latter part of which the user is frozen out of the UI.
    >
    > Let's say, though, that you wanted to pursue the approach that the above code represents. In that case, I probably *would* subclass the table view, and override keyDown: and keyUp:. You could simply set a flag at key down and clear it at key up, and use that information to control the way the performSelectors are timed. (It's still something of a hack, but at least it keeps the NSEvent details out of your selectionDidChange code.)
    >
    > However, once you get to this level of complexity, then it's probably worthwhile to spend your effort on moving the time-consuming part of the update process into a background thread instead. That would solve the problem for realz, letting keys repeat at their proper rate and letting updates happen as soon as they're ready, without freezes and artificial delays.
    >
    >> The objection to that was that I don't know what the current event is. If that is true, when [NSApp currentEvent] is useful? It seems that the current event will be the one that caused tableViewSelectionDidChange: to be called.
    >
    > It's useful when you know that you dequeued the last event and that you haven't dequeued another one since. In this case, you know that the table view dequeued a key event, but you don't know what it did after that. It *might* have created a background async GCD block to do the selection pre-processing, then executed a completion handler on the main thread to send the notification that triggered your method. Or, it *might* have looked ahead in the main event queue and dequeued some event.
    >
    > Even if you determined empirically that [NSApp currentEvent] gives you the key event, you can't be sure it will continue to do so in future frameworks versions.

    Thanks again. Ideally, the update method would just be fast but it is now as fast as it is going to get. The problem that I want to solve is delay update during key-repeat (which is something that Mail does very well).

    I subclassed NSTableView adding BOOL isRepeating. I set isRepeating = YES in keyDown: if isRepeating == NO && event isARepeat and isRepeating = NO in keyUp:. I replaced my tableViewSelectionDidChange: with the following.

        CMTableView *tableview = [notification object];

        NSTimeInterval delayInterval = 0.0;
        if(tableview.isRepeating)
        {
            delayInterval = [NSEvent keyRepeatInterval] + 0.01;
        }

        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(selectionDidChange) object:nil];
        [self performSelector:@selector(selectionDidChange) withObject:nil afterDelay:delayInterval];

    If not repeating, update right now. If repeating then delay. Now, the delay is in affect if repeating so tying it to keyRepeatInterval makes sense. Otherwise, the table updates right away. This is working great. What do you think?

    Thanks again for the help. It's been really valuable. Events and Run Loop are weak areas for me so I'm learning a lot here.

    Marc
  • On May 4, 2012, at 12:39 , Marc Respass wrote:

    > This is working great. What do you think?

    "Working great" sounds great!

    If you want to go the extra mile for your users, you could try something like this:

        CMTableView *tableview = [notification object];

        NSTimeInterval delayInterval = 0.0;
        if(tableview.isRepeating)
        {
    [detailView setEnabled: NO]; // or something that "dims" the detail view(s)
    [spinningProgressIndicatorOnTopOfDetailView: setHidden: NO];
    [spinningProgressIndicatorOnTopOfDetailView: startAnimating];
            delayInterval = [NSEvent keyRepeatInterval] + 0.01;
        }

        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(selectionDidChange) object:nil];
        [self performSelector:@selector(selectionDidChange) withObject:nil afterDelay:delayInterval];

    and add something like the following to the *end* of your performed method:

    [detailView setEnabled: YES];
    [spinningProgressIndicatorOnTopOfDetailView: setHidden: YES];
    [spinningProgressIndicatorOnTopOfDetailView: stopAnimating];

    (Note that there's no need to condition this last on how you got there. It's always safe to do.)

    At least, you could try it and see if they like it.
previous month may 2012 next month
MTWTFSS
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
Go to today