Shingoo
Shingoo

Reputation: 836

Changing GUI from a thread that's not the main thread?

My idea is to have a view that is showing the user when something is calculated behind the scenes. It is a view with a height of 30px containing an UIActivityIndicatorView and an UILabel showing what is beeing calculated right now.

I tried to implement it by having a ActivityHandler with these methods:

- (void)newActivityStarted{
    [self performSelectorOnMainThread:@selector(showActivityViewer) withObject:nil waitUntilDone:NO];
}

- (void)activityStopped{
    [self performSelectorOnMainThread:@selector(hideActivityViewer) withObject:nil waitUntilDone:NO];
}

- (void)changeInfoText:(NSString*)infoText{
    [activityView.infoLabel performSelectorOnMainThread:@selector(setText:) withObject:infoText waitUntilDone:NO];
}

Here are the methods that are called on the main thread:

-(void)hideActivityViewer{
    CGContextRef context = UIGraphicsGetCurrentContext();
    [UIView beginAnimations:nil context:context];

    [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
    [UIView setAnimationDuration:0.2];
    [UIView setAnimationDelegate: self];
    [UIView setAnimationDidStopSelector:@selector(closeActivityViewer:finished:context:)];
    [activityView setFrame:CGRectMake(320, 10, 320, 30)];

    [UIView commitAnimations];
}

-(void)closeActivityViewer:(NSString*)id finished:(BOOL)finished context:(id)context{

    [activityView removeFromSuperview];
    [activityView release];
    activityView = nil;
}

-(void)showActivityViewer{

    activityView = [[BCActivityView alloc] initWithFrame:CGRectMake(320, 10, 320, 30)];
    [activityView.infoLabel setText:NSLocalizedString(@"WorkInProgressKey",nil)];

    [[(MyAppDelegate*)[[UIApplication sharedApplication] delegate] window] addSubview: activityView];

    CGContextRef context = UIGraphicsGetCurrentContext();
    [UIView beginAnimations:nil context:context];

    [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
    [UIView setAnimationDuration:0.2];
    [activityView setFrame:CGRectMake(0, 10, 320, 30)];

    [UIView commitAnimations];
}

The methods newActivityStarted, activityStopped, changeInfoText are called by a background thread that is calculating some time consuming data.

The time consuming thread will be opened like this:

NSInvocationOperation* operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(calculateData) object:nil];
[[[NSOperationQueue new] autorelease] addOperation:operation];
[operation release];

The calculation is done like this:

-(void) calculateData{
    [[ActivityIndicatorHandler instance] newActivityStarted];
    [[ActivityIndicatorHandler instance] changeInfoText:NSLocalizedString(@"InitializingForecastKey", nil)];

    //Do something time consuming

    [[ActivityIndicatorHandler instance] activityStopped];
}

The effect is, that the activity view is shown and hidden after the whole calculation is done. I expected the GUI to show the view and be responsible to user interaction while the calculation is done in the background. But even if I have a breakpoint in the calculation, the view will not be usable until the calculation thread finished its work, as well as the activity view is not shown... And I can't figure out what's wrong...

Has anybody an idea?

Upvotes: 1

Views: 1273

Answers (3)

HiveHicks
HiveHicks

Reputation: 2334

I guess you need to consider performSelectorOnMainThread:withObject:waitUntilDone: method

Upvotes: 0

Chip Coons
Chip Coons

Reputation: 3201

@implementation ActivityHandler;

//....
- (void)newActivityStarted{
    [self performSelectorOnMainThread:@selector(showActivityViewer) withObject:nil waitUntilDone:NO];
}

- (void)activityStopped{
    [self performSelectorOnMainThread:@selector(hideActivityViewer) withObject:nil waitUntilDone:NO];
}

- (void)changeInfoText:(NSString*)infoText{
    [activityView.infoLabel performSelectorOnMainThread:@selector(setText:) withObject:infoText waitUntilDone:NO];
}


- (NSInvocationOperation*)myOperation:
{
    NSInvocationOperation* operation = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(calculateData) object:nil] autorelease];
    return operation;
}

In some other method, probably your app delegate, have a local queue set up in your initializer:

NSOperaionQueue* aQueue = [[[NSOperationQueue alloc] init] retain];  //release in dealloc
ActivityHandler* myHandler [[[ActivityHandler alloc] init] retain];  //release in dealloc

Have a method to add ops to the queue:

-(void)queueOperation:(NSInvocationOperation*)anOp;
{
    [aQueue addOperation:anOp];
}

-(IBAction*)startCalcs:(id)sender;
{
    [self queueOperation:[myHandler myOperation]];

}

Upvotes: 2

Felix Lamouroux
Felix Lamouroux

Reputation: 7494

Did you try to invoke the calculation by doing this instead:

[self performSelectorInBackground:@selector(calculateData) withObject:nil];

Upvotes: 0

Related Questions