Atualizando a Interface Gráfica durante um processo demorado no iOS

Já faz algum tempo que não colocamos uma dica de Objective-C aqui no Zeletron. Deixo abaixo uma dica legal.

Indicar que um processo demorado está sendo executado e que o aplicativo não está travado é uma das indicações do HIG (Human Interface Guidelines) do iOS. Para isso existem indicadores de atividade e barras de progresso, que indicam ao usuário que o programa está em execução ou o quanto falta para terminar a tarefa (no caso do ProgressView).

Activity Indicator
Activity Indicator
Progress View
Progress View

O problema é que qualquer alteração na interface gráfica precisa ser feita na Thread principal do programa e, se o processo demorado está rodando na thread principal, você vai ter problemas para atualizar um ProgressView ou mostrar um ActivityIndicator.

A dica é simples, você separa o código demorado numa thread em background e, sempre que precisar atualizar a interface gráfica, manda o programa fazer isso na thread principal. Assim, ó:

//Evento de um botão de "salvar"
- (IBAction)eventoBotaoSalvar:(id)sender {
    //Manda salvar numa thread em background
    [NSThread detachNewThreadSelector:@selector(salvaUmMonteDeCoisas) 
                             toTarget:self 
                           withObject:nil];
}
 
//--------------------------------------------------
//Dois métodos simples que atualizam a interface gráfica
//--------------------------------------------------
//Mostra o Activity Indicator
- (void)mostraActivityIndicator{
    self.meuActivityIndicator.hidden = NO;
}
 
//Esconde o Activity Indicator
- (void)escondeActivityIndicator{
    self.meuActivityIndicator.hidden = YES;
}
//--------------------------------------------------
 
//Salva a tralha toda
- (void)salvaUmMonteDeCoisas{
    //Mostra o ActivityIndicator antes de começar
    [self performSelectorOnMainThread:@selector(mostraActivityIndicator) 
                           withObject:nil 
                        waitUntilDone:NO];
 
    //Salva tudo (pode demorar à vontade)
    for (NSDictionary *d in meuArrayDeDicionarios){
        [self salvaDicionario:d];
    }
 
    //Depois de terminar tira o ActivityIndicator
    [self performSelectorOnMainThread:@selector(escondeActivityIndicator) 
                           withObject:nil 
                        waitUntilDone:NO];
}

Dessa forma, o programa não fica travado enquanto você está salvando aquele monte de dados. Você pode, inclusive, criar o ActivityIndicator na status bar do aparelho e deixar o usuário fazer outras coisas enquanto o App está salvando.

O código acima é só um exemplo de como podemos usar o detachNewThreadSelector e o performSelectorOnMainThread. Você pode encontrar, eventualmente, erros de sintaxe nele, mas a ideia é essa: Manda executar uma tarefa demorada em background e, sempre que precisar, atualiza a interface na thread principal.

Adding iAd in Phonegap Apps

How to add iAd to PhoneGap app: (Este post está em inglês para ajudar os desenvolvedores PhoneGap em todo mundo)

Step 1)

Modify the following function in yourAppDelegate.m:

From:

- (void)webViewDidFinishLoad:(UIWebView *)theWebView
{
	return [ super webViewDidFinishLoad:theWebView ];
}

To:

- (void)webViewDidFinishLoad:(UIWebView *)theWebView
{
	bannerIsVisible = YES;
	ADBannerView *adView = [[ADBannerView alloc] initWithFrame:CGRectZero];
	adView.frame = CGRectMake(0, 410, 320, 50); // if you want the banner to be on top of the screen remove this line
	adView.delegate = self;
	adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifier320x50;
	[theWebView addSubview:adView];
	return [ super webViewDidFinishLoad:theWebView ];
}

Step 2:

Modify the yourAppDelegate.h:

From:

@interface Desafio3x3AppDelegate : PhoneGapDelegate {
}

To:

#import "iAd/iAd.h"
 
@interface Desafio3x3AppDelegate : PhoneGapDelegate <ADBannerViewDelegate> {
	BOOL bannerIsVisible;
}

Step 3:

Add the following methods before the @end in yourAppDelegate.m

- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
	if (bannerIsVisible)
    {
        [UIView beginAnimations:@"animateAdBannerOff" context:NULL];
	// assumes the banner view is at the bottom of the screen.
        banner.frame = CGRectOffset(banner.frame, 0, 50); // if the banner is on top of the screen use -50
        [UIView commitAnimations];
        bannerIsVisible = NO;
    }
}
 
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
    if (!bannerIsVisible)
    {
        [UIView beginAnimations:@"animateAdBannerOn" context:NULL];
	// assumes the banner view is offset -50 pixels so that it is not visible.
        banner.frame = CGRectOffset(banner.frame, 0, -50); // if the banner is on top of the screen use 50
        [UIView commitAnimations];
        bannerIsVisible = YES;
    }
}
 
- (BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave
{
    NSLog(@"Banner view is beginning an ad action");
    BOOL shouldExecuteAction = YES; // your application implements this method if you want it not fixed
    if (!willLeave && shouldExecuteAction)
    {
        // insert code here to suspend any services that might conflict with the advertisement
    }
    return shouldExecuteAction;
}

Step 4:

Add the iAd Framework to the list of Frameworks of your project.

Ready!

Don’t forget to enable iAds when you upload your app.