views:

200

answers:

6

I have an IBAction with some simple code inside:

-(IBAction)change:(id)sender {
    [textfield setHidden:NO];
    [self dolengthyaction];
}

'textfield' is an NSTextField in a nib file, and -'dolengthyaction' is a function that takes about a minute to finish executing.

My question is: Why isn't the textfield shown until AFTER "dolengthyaction" is done executing? I want it to be revealed before the dolengthyaction starts taking place. Is this an inherent problem or is there something wrong with my code? (or in another part of my code?)

I'm still not very good at programming so I apologize if I worded something badly and formatted something wrong.

EDIT: There isn't much else other than this IBAction and -dolengthyaction...

-(void)doLengthyAction {
    sleep(10);
}
-(IBAction)change:(id)sender {
    [textfield setHidden:NO];
    [self doLengthyAction];
    [textfield setHidden:YES];
}

All I really want to do is display the label when the action is running and hide it when it's done.

Basically what this means is that it's not displaying at all right now.

In reality, in -doLengthyAction it's not sleep(10) but rather a NSFileManager operation that copies about 50 Mb of material. The code was rather long, but if you want me to post it I can. I tested it with sleep() but it doesn't work either.

+1  A: 

I think there is something wrong with the rest of your code. This should not happen.

Post more?

Chris Cooper
+1  A: 

Can't say what exactly is transpiring with out looking at rest of the code, but a hackish way would be to try:

-(IBAction)change:(id)sender {
   [textfield setHidden:NO];
   [self performSelector:@selector(doLenghtyAction) withObject:nil afterDelay:0.1]; --> or maybe even 0.0
 }
Mihir Mathuria
That is a nice suggestion that I would use in the future but I'm trying to hide the textfield as soon as the lengthy thing is done executing. I tried your suggestion and it hides the textfield again pretty much immediately if I stick a [textfield setHidden:YES] at the end of it. :)
Nano8Blazex
Oh right. Never mind my comment. Horace had a similar answer that solved my first comment's issue. +1 though :D
Nano8Blazex
+1  A: 
-(void)doLengthyAction {
     sleep(10);
    [textfield setHidden:YES];
} 

-(IBAction)change:(id)sender {
    [textfield setHidden:NO];
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(doLengthyAction) userInfo:nil repeats:NO];
}
ohho
+1 Nice workaround, although it would be nice to know why the original doesn't work.
Nano8Blazex
the original: setHidden > doLengthyAction > setHidden does not allow any time slot for the UI to refresh. the state of the textfield is indeed changed twice. however, the first change was not refreshed on screen. when the UI has a chance to refresh the screen, the textfield is already at the 2nd state.
ohho
I see. So this isn't my fault, it's just an inherent thing in Cocoa. I see. :D thanks!
Nano8Blazex
It does not work like Visual Basic ;-)
ohho
+5  A: 

All drawing operations (including hiding and showing views) are triggered from the run loop. The run loop cannot perform the next event until after your function returns.

If you have an operation that takes more than a second to run, you should perform it in a thread. Once it completes, use performSelectorOnMainThread to make UI changes on the main thread.

drawnonward
I see. That explains it too.
Nano8Blazex
The above answer is slightly misleading. You can force drawing to happen outside of the run loop by simply calling [window display].
Leibowitzn
@Liebowitzn I was thinking iPhone, which limits you to passive drawing. Good catch.
drawnonward
+5  A: 

As mentioned in some of the previous answers, the application must return to the main run loop before it will redraw (it's an optimization to avoid redrawing when you make many changes).

You really should handle things on a background thread if they're going to run for a long time. If you don't the UI will beach ball while your operation runs.

If you're targeting 10.6+, you can use GCD and blocks as follows to run things easily in the background (and not have to define multiple methods to get things done):

-(void)doLengthyAction {
    sleep(10);
}
-(IBAction)change:(id)sender {
    [textfield setHidden:NO];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self doLengthyAction];
        dispatch_async(dispatch_get_main_queue(), ^{
            [textfield setHidden:YES];
        });
    });
}

Of course by using this, you'll need to make sure that what's going on in the lengthy action is thread safe, but you'll end up with a better user experience. For more info on this type of CGD code, you might want to read this.

wbyoung
A: 

Try this:

-(IBAction)change:(id)sender {
    [textfield setHidden:NO];
    [[textfield window] display]; // force the window to update
    [self doLengthyAction];
    [textfield setHidden:YES];
}
Leibowitzn