2010-03-06 5 views
1

Mon idée est d'avoir une vue qui montre à l'utilisateur quand quelque chose est calculé dans les coulisses. C'est une vue avec une hauteur de 30px contenant un UIActivityIndicatorView et un UILabel montrant ce qui est calculé en ce moment.Changer l'interface graphique d'un thread qui n'est pas le thread principal?

J'ai essayé de le mettre en œuvre en ayant un activityHandler avec ces méthodes:

- (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]; 
} 

Voici les méthodes qui sont appelées sur le thread principal:

-(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]; 
} 

Les méthodes newActivityStarted, activityStopped, changeInfoText sont appelés par un thread d'arrière-plan qui calcule des données chronophages.

Le temps fil consommation sera ouvert comme ceci:

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

Le calcul se fait comme suit:

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

    //Do something time consuming 

    [[ActivityIndicatorHandler instance] activityStopped]; 
} 

L'effet est que le point de vue de l'activité est affichée et cachée après l'ensemble le calcul est fait. Je m'attendais à ce que l'interface graphique affiche la vue et soit responsable de l'interaction de l'utilisateur pendant que le calcul est effectué en arrière-plan. Mais même si j'ai un point d'arrêt dans le calcul, la vue ne sera pas utilisable tant que le thread de calcul n'aura pas terminé son travail, et la vue d'activité ne sera pas montrée ... Et je n'arrive pas à comprendre ...

Quelqu'un a-t-il une idée?

Répondre

2
@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; 
} 

Dans une autre méthode, probablement votre délégué d'application, une file d'attente locale mis en place dans votre initialiseur:

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]]; 

} 
+0

Les messages conservés après l'alloc est inutile. Cela crée juste une fuite de mémoire. – Giao

+0

Bien sûr. J'ai changé la réponse pour faire myHandler un ivar du délégué de l'application et mieux montrer mon intention. –

+0

Merci pour la réponse détaillée. Si je compare nos solutions, il semble que la seule différence est que votre file d'attente est référencée par une variable locale. Je vais essayer cette solution et revenir sur mes résultats. Mais qu'est-ce que cela change? – Shingoo

0

Avez-vous essayé d'invoquer le calcul en faisant ceci:

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

Je suppose que vous devez considérer performSelectorOnMainThread: withObject: waitUntilDone: méthode

Questions connexes