UIWebView con sfondo trasparente [AGGIORNATO]

Talvolta è necessario usare una UIWebView per visualizzare un testo formattato in maniera particolare, con immagini ed altri elementi inclusi. Si tratta di un modo semplice senza ricorrere a RTF o PDF.

In questo caso allora uno sfondo trasparente è l’ideale.

Tre sono le cose da fare per ottenere una webview con sfondo trasparente:

  1. Impostare il Background dell’oggetto UIWebView a Clear*
  2. Disattivare l’opzione Opaque dell’oggetto UIWebView
  3. Settare il CSS (o l’attributo HTML) in modo che il tag body abbia background-color: transparent.

*Aggiornamento: se la UIWebView è all’interno di una UITableViewCell, questa proprietà va settata, a causa di un bug in UIKit, nel metodo

1
-(void)setSelected:(BOOL)selected animated:(BOOL)animated
-(void)setSelected:(BOOL)selected animated:(BOOL)animated

altrimenti, in iOS 4, lo sfondo sarà grigio (fonte).

Deselezionare una riga di tabella

Può capitare di non voler lasciare selezionata una riga di tabella quando si torna alla view che la conteneva (ad esempio al tocco del tasto Back o alla dismissione di una view modale). Qualunque sia il momento in cui vogliamo che la deselezione abbia luogo (in viewWillAppear o altro), e qualunque sia la riga da deselezionare, questo è il codice più semplice per farlo:

1
 [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:NO];
 [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:NO];

ovvimente posto che la vostra tabella si chiami tableview (questo è sempre vero se il view controller è stato creato direttamente come UITableViewController), altrimenti usate il giusto nome della tabella.

Come sempre accade, i metodi forniti da Apple sono autodescrittivi, e non credo che serva spiegarne il funzionamento.

Bloccare un performSelector prima che l’azione venga eseguita

Come abbiamo visto, NSTimer è un ottima classe per gli eventi ripetuti nel tempo, mentre performSelector è molto più semplice quando l’azione è solo ritardata nel tempo, ma si deve svolgere una volta sola.

Quando si deve interrompere un timer è sufficiente tenerne traccia, ad esempio con una variabile di istanza (o proprietà), e chiamare il metodo invalidate.

Un performSelector invece va bloccato con il seguente metodo di classe:

1
[NSObject cancelPreviousPerformRequestsWithTarget:];
[NSObject cancelPreviousPerformRequestsWithTarget:];

oppure, per un blocco più mirato:

1
[NSObject cancelPreviousPerformRequestsWithTarget: selector: object:
[NSObject cancelPreviousPerformRequestsWithTarget: selector: object:

Se ci sono più richieste identiche allora non c’è altro modo per eliminarne una se non quella di tornare alla NSTimer, senza ripetizioni, tenendo traccia del singolo timer.

Implementare un Protocollo

Implementare un protocollo può essere una strada molto semplice per fare in modo che un oggetto, che non è in grado di compiere direttamente una azione, possa delegare qualche altro oggetto per compierla. Tale oggetto delegante dovrà quindi dichiarare un protocollo e i suoi metodi, mentre il delegato dovrà dichiarare di essere conforme al protocollo, implementandone i metodi.

Se su una app per iPad, giusto per fare un esempio, un popover permette di settare una variabile nella view che lo ospita, allora possiamo dichiarare un protocollo, un metodo e un delegato nel file di intestazione del popover:

1
2
3
4
5
6
7
8
9
@protocol SetVariableDelegate <NSObject>
 
-(void)setVariable:(int)variable;
 
@end
 
@interface VariableViewController : UITableViewController
 
@property (nonatomic, retain) id<SetVariableDelegate> delegate;
@protocol SetVariableDelegate <NSObject>

-(void)setVariable:(int)variable;

@end

@interface VariableViewController : UITableViewController

@property (nonatomic, retain) id<SetVariableDelegate> delegate;

mentre nel file di implementazione faremo in modo che un certo evento lanci il metodo di protocollo sul delegato:

1
[self.delegate setVariable:value];
[self.delegate setVariable:value];

Nella view, invece, dovremo dichiarare che questa è conforme al protocollo:

1
@interface ViewController : UIViewController <SetVariableDelegate>
@interface ViewController : UIViewController <SetVariableDelegate>

avendo cura di importare la classe (anche se è facile che sia già stata importata per altri motivi), e nel file .m implementare il metodo di protocollo

1
2
3
4
-(void)setVariable:(int)variable{
    //fare qualcosa con variable
 
}
-(void)setVariable:(int)variable{
    //fare qualcosa con variable

}

ricordando, al momento della creazione del popover, di settare tale view come delegato del popover:

1
popover.delegate = self;
popover.delegate = self;

Questo pattern di programmazione ad oggetti è molto utile quando, come nell’esempio del popover, una azione su un oggetto che viene distrutto deve scaturire anche altre azioni sull’oggetto che l’ha creato. Senza i protocolli ci troveremmo nella sgradivo situazione di dover far compiere ad un oggetto defunto una azione non ovviamente non può più compiere.

Quando invece gli oggetti continuano ad esistere, il protocollo è utile ad esempio per delegare un controller nell’impostazione di un oggetto, come nel caso di una UITableView che può delegare un UIViewController che la contiene a reagire al tocco di una riga (UITableViewDelegate) o di definirne il contenuto (UITableViewDataSource). In quest’ultimo caso il metodo di protocollo non è void ma restituisce un valore, ad esempio un intero, usato dalla tabella per sapere quante righe deve contenere, quante righe per ogni sezione eccetera.

N.B.: alcuni metodi del protocollo si possono impostare come obbligatori: chiunque dichiari di essere conforme deve implementarli.

Generare un numero intero casuale

La funzione preferita per generare un numero casuale è arc4random(). Per usarla quando si vuole generare un intero compreso fra x e y è possibile usare calcolare l’intervallo da x a y, ovvero y-x e usare questa formula

1
int randomNumber = arc4random() % range + x
int randomNumber = arc4random() % range + x

Come ulteriore spunto, se il numero casuale dev’essere un numero di n cifre, allora

1
2
int x = (int) pow(10, 10-1);
int range = (int) ( pow(10, n) - 1) - x;
int x = (int) pow(10, 10-1);
int range = (int) ( pow(10, n) - 1) - x;

se ad esempio volessimo un numero compreso fra 1000 e 9999 (quindi 4 cifre), avremo

1
2
x = 1000
range = (10^4 - 1) - 10^3 = 9999 - 1000 = 8999
x = 1000
range = (10^4 - 1) - 10^3 = 9999 - 1000 = 8999

 

Fate attenzione al valore massimo che un tipo di variabile può assumere per evitare comportamenti anomali dell’algoritmo.

Configurazione di Sequel Pro con MySQL

Spesso App per iPhone e iPad devono comunicare con un server e se questo implica di dover immagazzinare informazioni su un database MySQL allora la creazione e la manutenzione di questo database può essere fatta con l’ottima applicazione Sequel Pro per Mac OS X.

Bisogna innanzitutto autorizzare il client ad accedere al database e questo, se si usa cPanel è abbastanza semplice. Dopo aver scoperto il proprio indirizzo IP (ad esempio con il widget IP) basterà inserirlo fra quelli autorizzati nella pagina Remote MySQL (sezione Databases). Oppure, se non si possiede un indirizzo IP statico e si vuole evitare di aggiungere IP autorizzati ad ogni riavvio del modem, si può usare la wildcard “%” per autorizzare tutti gli IP (tanto l’accesso è consentito solo a chi conosce nome utente e password)

Dopo di che, al primo avvio di Sequel Pro bisognerà configurare al connessione con il nostro database residente su server. Oltre al nome utente e password di accesso al database (che dovremmo aver ricevuto dal fornitore del servizio o dal responsabile IT) c’è l’Host da dover inserire. Questo non è altro che l’indirizzo IP del server e lo ricaviamo sempre dal nostro fornitore del servizio oppure, ad esempio, inserendo il nome del dominio in un servizio web che converte domini in IP.

A questo punto il gioco è fatto e la connessione al database MySQL può essere effettuata.

Ordinare un array di dizionari

Spesso si usano NSDictionary dentro degli NSArray per sopperire al fatto che gli array di CoreFoundation non hanno chiavi. Ma come si fa allora, ad esempio, a ordinare un array di dizionari (dizionari che hanno più di una chiave)?

Il metodo è molto più semplice di quello che si potrebbe pensare, grazie a una classe ed un metodo appositamente creato da Apple:

1
2
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"Chiave"  ascending:YES];
    [aMutableArray sortUsingDescriptors:[NSArray arrayWithObjects:descriptor,nil]];
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"Chiave"  ascending:YES];
    [aMutableArray sortUsingDescriptors:[NSArray arrayWithObjects:descriptor,nil]];

Come è facilmente intuibile, un array di descrittori possono stabilire una sequenza di criteri di ordinamento, alcuni ascendenti, altri discendenti.

Errori nella Console durante l’uso di AVFoundation nel Simulatore

Se utilizzate il framework AVFoundation per la riproduzione di suoni e video sappiate che gli errori, comparsi nella Console da quando avete aggiornato ad iOS 5, sono perfettamente ignorabili e innocui.

Ecco un piccolo stralcio degli errori che potreste leggere:

1
Error loading /System/Library/Extensions/AudioIPCDriver.kext/Contents/Resources/AudioIPCPlugIn.bundle/Contents/MacOS/AudioIPCPlugIn:  dlopen(/System/Library/Extensions/AudioIPCDriver.kext/Contents/Resources/AudioIPCPlugIn.bundle/Contents/MacOS/AudioIPCPlugIn, 262): Symbol not found: ___CFObjCIsCollectable
Error loading /System/Library/Extensions/AudioIPCDriver.kext/Contents/Resources/AudioIPCPlugIn.bundle/Contents/MacOS/AudioIPCPlugIn:  dlopen(/System/Library/Extensions/AudioIPCDriver.kext/Contents/Resources/AudioIPCPlugIn.bundle/Contents/MacOS/AudioIPCPlugIn, 262): Symbol not found: ___CFObjCIsCollectable

Un indizio di quanto questi errori siano in realtà ignorabili sta nel fatto che se non eseguite l’app nel Simulatore, bensì sul dispositivo vero e proprio, vi accorgerete che questi errori scompariranno. Probabilmente si tratta solamente di log prolissi dovuti all’assenza di un reale dispositivo audio video come quello di un iPhone o iPad. È anche probabile che in futuri aggiornamenti di Xcode e/o iOS questa prolissità venga attenuata.

Visualizzare un campo di testo in un alert alla maniera Apple

C’è una piccola differenza grafica fra i semplici textfield e quelli che usa Apple quando, ad esempio ci chiede la password del nostro Apple ID. Una differenza molto gradevole e che Mike Piontek ha provato a colmare nel 2008 grazie ad un file png che potete importare nel vostro progetto

e ad un codice che Mike ha implementato come segue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
UIAlertView *passwordAlert = [[UIAlertView alloc] initWithTitle:@“Server Password” message:@”\n\n\n”
delegate:self cancelButtonTitle:NSLocalizedString(@“Cancel”,nil) otherButtonTitles:NSLocalizedString(@“OK”,nil), nil];
 
UILabel *passwordLabel = [[UILabel alloc] initWithFrame:CGRectMake(12,40,260,25)];
passwordLabel.font = [UIFont systemFontOfSize:16];
passwordLabel.textColor = [UIColor whiteColor];
passwordLabel.backgroundColor = [UIColor clearColor];
passwordLabel.shadowColor = [UIColor blackColor];
passwordLabel.shadowOffset = CGSizeMake(0,-1);
passwordLabel.textAlignment = UITextAlignmentCenter;
passwordLabel.text = @“Account Name”;
[passwordAlert addSubview:passwordLabel];
 
UIImageView *passwordImage = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@“passwordfield” ofType:@“png”]]];
passwordImage.frame = CGRectMake(11,79,262,31);
[passwordAlert addSubview:passwordImage];
 
UITextField *passwordField = [[UITextField alloc] initWithFrame:CGRectMake(16,83,252,25)];
passwordField.font = [UIFont systemFontOfSize:18];
passwordField.backgroundColor = [UIColor whiteColor];
passwordField.secureTextEntry = YES;
passwordField.keyboardAppearance = UIKeyboardAppearanceAlert;
passwordField.delegate = self;
[passwordField becomeFirstResponder];
[passwordAlert addSubview:passwordField];
 
[passwordAlert setTransform:CGAffineTransformMakeTranslation(0,109)];
[passwordAlert show];
[passwordAlert release];
[passwordField release];
[passwordImage release];
[passwordLabel release];
UIAlertView *passwordAlert = [[UIAlertView alloc] initWithTitle:@“Server Password” message:@”\n\n\n”
delegate:self cancelButtonTitle:NSLocalizedString(@“Cancel”,nil) otherButtonTitles:NSLocalizedString(@“OK”,nil), nil];

UILabel *passwordLabel = [[UILabel alloc] initWithFrame:CGRectMake(12,40,260,25)];
passwordLabel.font = [UIFont systemFontOfSize:16];
passwordLabel.textColor = [UIColor whiteColor];
passwordLabel.backgroundColor = [UIColor clearColor];
passwordLabel.shadowColor = [UIColor blackColor];
passwordLabel.shadowOffset = CGSizeMake(0,-1);
passwordLabel.textAlignment = UITextAlignmentCenter;
passwordLabel.text = @“Account Name”;
[passwordAlert addSubview:passwordLabel];

UIImageView *passwordImage = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@“passwordfield” ofType:@“png”]]];
passwordImage.frame = CGRectMake(11,79,262,31);
[passwordAlert addSubview:passwordImage];

UITextField *passwordField = [[UITextField alloc] initWithFrame:CGRectMake(16,83,252,25)];
passwordField.font = [UIFont systemFontOfSize:18];
passwordField.backgroundColor = [UIColor whiteColor];
passwordField.secureTextEntry = YES;
passwordField.keyboardAppearance = UIKeyboardAppearanceAlert;
passwordField.delegate = self;
[passwordField becomeFirstResponder];
[passwordAlert addSubview:passwordField];

[passwordAlert setTransform:CGAffineTransformMakeTranslation(0,109)];
[passwordAlert show];
[passwordAlert release];
[passwordField release];
[passwordImage release];
[passwordLabel release];

Notate come il codice non faccia uso dell’Automatic Reference Counting (ARC). Se il vostro progetto lo implementa, eliminate tutti i release.

Accedere all’AppDelegate

Dovunque ci si trovi nel codice, l’AppDelegate è sempre raggiungibile trmite il singleton sharedApplication:

1
MainViewController *appDelegate = (MainViewController *)[[UIApplication sharedApplication] delegate];
MainViewController *appDelegate = (MainViewController *)[[UIApplication sharedApplication] delegate];

In questo modo qualsiasi proprietà dichiarata in AppDelegate.h sarà virtualmente raggiungibile e qualsiasi metodo richiamabile.