Copia&Incolla su iOS

UIPasteboard è la classe che Apple mette a disposizione degli sviluppatori per usufruire della comoda funzione di copia e incolla via codice. I cosiddetti “appunti” possono essere richiamati da qualunque applicazione ed è questa la loro principale fonte di utilità.

Per fare questo si possono usare una delle seguenti 5 proprietà:

1
2
3
4
NSString *string = pasteboard.string;
UIImage *image = pasteboard.image;
NSURL *url = pasteboard.URL;
UIColor *color = pasteboard.color;
NSString *string = pasteboard.string;
UIImage *image = pasteboard.image;
NSURL *url = pasteboard.URL;
UIColor *color = pasteboard.color;

Per una guida esaustiva, vedere qui.

Sommare due NSNumber

Può sembrare una domanda banale, ma eseguire operazioni su un NSNumber richiede un minimo di attenzione dal momento che stiamo parlandoci oggetti e non di variabili scalari.

Per sommare due NSNumber, infatti, bisogna prima trasformarli in scalari (ad esempio float) e poi usare il risultato per creare un nuovo NSNumber object. Ecco l’esempio:

1
NSNumber *sum = [NSNumber numberWithFloat:([one floatValue] + [two floatValue])];
NSNumber *sum = [NSNumber numberWithFloat:([one floatValue] + [two floatValue])];

Generare un numero casuale

La generazione di un numero casuale, utile soprattutto nei giochi, è possibile tramite ad esempio la funzione arc4random(). Ecco come funziona:

Eseguendo il modulo n del valore restituito si determina l’ampiezza n dell’intervallo dei numeri casuali generati. Quindi ad esempio:

1
int x = arc4random() % 100;
int x = arc4random() % 100;

genera un numero casuale compreso fra 0 e 99.

Mentre aggiungere un valore intero determina ovviamente il valore iniziale. Quindi ad esempio:

1
int y = (arc4random() % 501) + 500;
int y = (arc4random() % 501) + 500;

genera un numero casuale compreso fra 500 e 1000.

Se quindi vogliamo ottenere un valore casuale compreso fra min e max possiamo scrivere

1
int z = (arc4random() % (max - min + 1) ) + min;
int z = (arc4random() % (max - min + 1) ) + min;

 

Verificare che una stringa contenta un valore numerico

Per verifica se una stringa rappresenta correttamente un numero si può usare la classe NSNumberFormatter. Infatti, avendo inizializzato un numberFormatter, si può usare il metodo

1
[numberFormatter numberFromString:string]
[numberFormatter numberFromString:string]

che restituisce nil se la NSString non è convertibile in numero.

Ordinare gli elementi di una IBOutletCollection

Le IBOutletCollection sono davvero molto utili. Permetto di raccogliere in un unico array una serie di oggetti grafici della stessa classe. In questo modo, se abbiamo una serie di UILabel o una serie di UIButton che ci aiutano, ad esempio, a rappresentare una griglia di una tabella o le carte di un gioco, possiamo sfogliare la collection per manipolare questi oggetti uno ad uno con un ciclo for.

Può capitare però che questi oggetti contenuti nella IBOutletCollection non siano in un ordine arbitrario ma che, ad esempio, debbano essere manipolati a seconda della posizione che occupano nello schermo. Ecco che allora possiamo usare questo codice o una sua variante:

1
2
3
4
5
self.labelsArray = [self.labelsArray sortedArrayUsingComparator:^NSComparisonResult(id label1, id label2) {
        if ([label1 frame].origin.y < [label2 frame].origin.y) return NSOrderedAscending;
        else if ([label1 frame].origin.y > [label2 frame].origin.y) return NSOrderedDescending;
        else return NSOrderedSame;
    }];
self.labelsArray = [self.labelsArray sortedArrayUsingComparator:^NSComparisonResult(id label1, id label2) {
        if ([label1 frame].origin.y < [label2 frame].origin.y) return NSOrderedAscending;
        else if ([label1 frame].origin.y > [label2 frame].origin.y) return NSOrderedDescending;
        else return NSOrderedSame;
    }];

In pratica si ordina l’array come qualsiasi altro array ma, trattandosi di oggetti presenti nell’interfaccia, possiamo ordinarli, ad esempio, secondo la propria posizione.

Sostituire un oggetto in una particolare posizione di un array

Ci sono molti casi in cui diventa necessario aggiungere elementi in NSMutableArray senza però usare il metodo addObject:, il quale, ovviamente, aggiunge l’oggetto in coda all’array, in ultima posizione. Si può usare quindi il metodo insertObject:atIndex. Ma ancora più utile è sapere che se, per esempio, si volesse sostituire un UIBarButtonItem in una ToolBar, non è necessario andare andando a cercare nell’array [items mutableCopy] l’UIBarButtonItem da eliminare, rimuoverlo con removeObjectAtIndex: e poi inserire quello nuovo con insertObject:atIndex. Si può infatti direttamente usare replaceObjectAtIndex:.

Sapere se la connessione è andata in timout

Usando NSURLConnection può capitare, per i più svariati motivi, che la connessione e il download dei dati non avvengano entro un certo tempo limite, detto timeout. Superato questo limite la connessione decade.

Per sapere se la connessione si è interrotta proprio per il raggiungimento del timeout si possono sfruttare le informazioni contenute nell’oggetto NSError usato per far partire la connessione, in questo modo:

1
2
3
4
5
6
NSError *error = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
 
if (error.code == NSURLErrorTimedOut) {
 
}
NSError *error = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

if (error.code == NSURLErrorTimedOut) {

}

nel creare la NSURLRequest, si può impostare questo valore di timeout, in secondi:

1
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30];

Creare un completion handler

Comodissimi sono i blocchi di codice eseguiti quando un certo metodo si conclude, in particolare animazioni e metodi in background, gestiti con GCD (Grand Central Dispatch) o direttamente da classi Apple, come NSURLConnection. Può essere quindi utile saper creare un completion handler, ovvero un blocco di codice che viene eseguito solo quando un metodo effettivamente si conclude, espandendo quindi le funzionalità in maniera importante.

Innanzitutto si dichiara il blocco di ritorno del completion handler, avendo cura di inserire gli eventuali oggetti e variabili da ritornare: ad esempio un codice che effettui il download di un  pacchetto di dati potrebbe restituire un oggetto NSData e/o un BOOL success:

1
typedef void(^ReturnBlock)(NSData *data, BOOL success);
typedef void(^ReturnBlock)(NSData *data, BOOL success);

Il completion handler può essere quindi una variabile di istanza di tipo ReturnBlock dichiarata nello stesso file d’interfaccia in cui si è definito il ReturnBlock:

1
2
3
4
@interface MyClass : NSObject
{
    ReturnBlock _completionHandler;
}
@interface MyClass : NSObject
{
    ReturnBlock _completionHandler;
}

Il metodo che fa uso di questo completio handler sarà qualcosa del genere

1
- (void)doSomethingWithCompletionHandler:(ReturnBlock)aCompletionHandler;
- (void)doSomethingWithCompletionHandler:(ReturnBlock)aCompletionHandler;

Nella cui implementazione, se ad esempio si sta usando la classe NSURLConnection per gestire il download, si scriverà nel metodo delegato:

1
2
3
4
5
6
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
 
if( _completionHandler )
    _completionHandler(data, YES);
 
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

if( _completionHandler )
    _completionHandler(data, YES);

}

Quindi, ovunque allochiamo e inizializiamo una istanza di MyClass, possiamo anche usare il seguente modo il metodo:

1
2
3
[myClass doSomethingWithCompletionHandler:^(NSData *data, BOOL success)^{
 
}];
[myClass doSomethingWithCompletionHandler:^(NSData *data, BOOL success)^{

}];

il blocco di codice verrà eseguito solo al completamento di qualunque cosa doSomething faccia, passando l’oggetto data e la variabile booleana success.

N.B.: con ARC non c’è bisogno di copiare il completion handler nel metodo doSomething né di rilasciarlo in dealloc.

Errore: CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary on line 23. Parsing will be abandoned. Break on _CFPropertyListMissingSemicolon to debug.

Se la Console mostra il seguente errore:

CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary on line 23. Parsing will be abandoned. Break on _CFPropertyListMissingSemicolon to debug.

 

e la localizzazione sembra non funzionare più, anche se il progetto viene compilato senza errori né  warning, allora vuol dire che avete dimenticato un punto e virgola all’interno del file Localizable.string.

Ricordo, con l’occasione, che usando la funzione

NSLocalizedString(@”Hello”, @”Comment”);

nel file Localizable.string (ad esempio nella sua versione italiana) bisogna scrivere:

“Hello” = “Ciao”;

Come non bloccare mai l’interfaccia con le NSOperation

Il problema del caricamento da internet o dell’elaborazione di un grande numero di dati è che l’interfaccia dell’applicazione si blocca facilmente e non risponde ai comandi dell’utente. Questo è dovuto al fatto che i metodi incriminati vengono tutti eseguiti nel thread principale, ovvero quello in cui gira anche il loop dell’interfaccia.

Possibili soluzioni sono quei metodi del tipo performSelectorInBackground:withObject: che però, all’atto pratico non garantiscono una fluidità dell’interfaccia in casi come il caricamento di una immagine da internet tramite il metodo dataWithContentsOfURL:.

Per risolvere definitivamente questo problema è necessario non solo che un metodo potenzialmente bloccante giri su un thread diverso dal MainThread, ma che lo faccia senza fretta e quindi senza pericolo che il sistema venga lo stesso bloccato.

Per questo Apple ha messo a disposizione la classe NSOperationQueue che permette di aggiungere ad una lista d’attesa una serie di metodi da eseguire in background. Appena sarà possibile, l’app le eseguirà una dietro l’altra. Il tempi di attesa possono risultare leggermente superiori, ma ne gioverà l’usabilità dell’interfaccia, con utenti più contenti.

Ecco un esempio del modo più semplice di usare questa classe:

1
2
3
4
5
self.queue = [NSOperationQueue new];
 
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadData) object:nil];
 
[self.queue addOperation:operation];
self.queue = [NSOperationQueue new];

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadData) object:nil];

[self.queue addOperation:operation];

Nell’esempio queue è una property dichiarata nel .h e quindi richiamabile in ogni parte del codice di implementazione.

È importante però ricordarsi che le modifiche all’interfaccia devono girare nel MainThread e che quindi, all’interno di un metodo inserito in una NSOperation bisogna usare il metodo performSelectorOnMainThread:withObject:waitUntilDone: ogni volta che serve di modificare l’interfaccia, impostando waitUntilDone a YES.