views:

260

answers:

2

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?

A: 

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

[self performSelectorInBackground:@selector(calculateData) withObject:nil];
Felix
+2  A: 
@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]];

}
Chip Coons
The retain messages after the alloc is unnecessary. That just creates a memory leak.
Giao
Sure does. I changed the answer to make myHandler an ivar of the app delegate and better show my intent.
Chip Coons
Thanks for the detailed answer. If I compare our solutions it seems like the only difference is, that your queue is referenced by a local variable. I'll try this solution an come back to tell my results. But what does this change?
Shingoo
BTW why do you retain the queue and the handler after the alloc/init?
Shingoo
I tried that and sadly it did not change anything. It is the same phenomenon. The view appears and immediately disappears after the calculation has finished.
Shingoo
What class is your -(void)calculateData method in, and how is it invoked? The three methods within it are called sequentially and are all in the same thread, so that might be causing them the be single threaded. I retained the queue and handler in the app delegate until it is finished so as to avoid a premature release of the objects.
Chip Coons
Have you tried making your handler class a subclass of NSOperation and overridden the required methods as detailed in http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Reference/NSOperation_class/Reference/Reference.html?
Chip Coons