Categoria ‘Mobile’
Su Apple iPhone e iPod eravamo abituati a gestire un solo file immagine al caricamento dell’applicazione, il file Default.png. Su Apple iPad, invece, la diversa gestione dell’orientamente impone l’adozione di più file immagine, per essere certi di visualizzare lo splash screen corretto in base all’orientamente del dispositivo. Durante l’avvio dell’applicazione, come accadeva per iPhone, non è infatti possibile intervenire da codice per “chiedersi” com’è orientato il dispositivo. Fortunatamente è stato introdotto in automatico il caricamento di speciali file a seconda dell’orientamento:

I file attualmente supportati, oltre al classico Default.png che sconsiglio di utilizzare in quanto viene ridimensionato e deformato in base all’orientamento, sono:
- Default-Portrait.png
- Default-PortraitUpsideDown.png
- Default-Landscape.png
- Default-LandscapeLeft.png
- Default-LandscapeRight.png
Le versioni PortraitUpsideDown, LandscapeLeft e LandscapeRight possono essere utilizzate per determinare orietamento e verso di quest’ultimo.
Ad applicazione avviata, poi, come consigliato da Apple, è bene “ridisegnare” – ove necessario – le nostre viste agendo all’interno di application:didFinishLaunchingWithOptions.
Continua...
Nella maggioranza dei casi, o perchè siamo abituati o perchè l’abbiamo visto in tutorial e in alcuni testi, utilizziamo il setAnimationDidStopSelector in questa maniera:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| [UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(removeView)];
myView.alpha = 0;
[UIView commitAnimations];
//
- (void) removeView {
[myView removeFromSuperview];
} |
Nel codice sopra esposto la setAnimationDelegate imposta self come delegato e tramite la setAnimationDidStopSelector gli invia un messaggio removeView quando l’animazione è terminata. Il codice è di per sé corretto, tuttavia fa uso di una definizione di messaggio (removeView) che potrebbe essere omessa. Guardate adesso lo stesso codice, con il medesimo effetto, senza il messaggio removeView:
1 2 3 4 5 6 7 8 9
| [UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDelegate:myView];
[UIView setAnimationDidStopSelector:@selector(removeFromSuperview)];
myView.alpha = 0;
[UIView commitAnimations]; |
La cosa interessante di questo approccio è che myView potrebbe essere una sottoclasse di una UIView! Potrebbe quindi essere una nostra classe personalizzata con propri messaggi ed, nel modo esposto, tranquillamente richiamabile da setAnimationDidStopSelector. Inoltre, la setAnimationDidStopSelector accetta selettori con parametri:
1 2 3 4 5 6 7 8 9
| [UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDelegate:myView];
[UIView setAnimationDidStopSelector:@selector(myMessage:param1:)];
myView.alpha = 0;
[UIView commitAnimations]; |
Questo esempio può essere esteso a tutti qui casi dove impostiamo un delegato che, atro non è, che un puntatore ad un’istanza di un oggetto qualsiasi.
Continua...
Dopo aver illustrato come localizzare le nostre stringhe in Xcode, vediamo adesso quanto risulta semplice – applicando la medesima tecnica – localizzare immagini e viste/interfacce realizzate con Interface Builder.
Localizzare risorse grafiche
Il processo, come accennato, è il medesimo; se abbiamo un’immagine già inserita nelle nostre risorse, o ne inseriamo una nuova, e vogliamo “localizzarla” – cioè gestire due o più immagini in base alle lingue supportate – basterà cliccare con il tasto destro sull’immagine (Adium.png in questo esempio) e selezionare Get Info:

Clicchiamo Make File Localizable in basso a sinistra.

Clicchiamo su Add Localization e inseriamo Italian:

In modo da ottenere:

La nostra immagine viene spostata (fisicamente, una delle rare volte in cui quello che accade nell’alberatura Xcode si riflette sul filesystem) sotto le cartelle virtuali English.lproj e Italian.lproj esattamente come accadeva con il testo:

A questo punto ognuna delle cartelle English.lproj e Italian.lproj contiene una stessa versione dell’immagine. Questa immagine è manipolabile all’interno di Interface Builder, dove ne vedremo – per default – la versione inglese.
Arrivati a questo punto, basta sovrascrivere uno (o entrambi i file Adium.png) per ottenere una localizzazione delle immagini “lampo”.
Localizzare i file XIB
Anche le interfacce costruite con Interface Builder possono essere localizzate nella loro interezza, quando lo si ritiene necessario. Il procedimento è identico a quello svolto con le risorse grafiche: aggiungiamo un ViewController (ad esempio infoViewController) con interfaccia XIB, lo selezioniamo, scegliamo Get Info dal menu contestuale, rendiamo il file localizzabile, aggiungiamo la localizzazione in italiano:

Cliccando su English o su Italian si aprirà Interface Builder! Questa volta, sul filesystem, noteremo che sono state create due cartelle (English.lproj e Italian.lproj) all’interno della classica cartella Classes. Entrambe avranno il loro file infoViewController.xib. La comodità, in questo, risolta evidente nel codice; quando andremo ad istanziare il nostro controller si avrà un codice “pulito” di questo tipo:
1 2
| InfoViewController *info = [InfoViewController alloc];
[self.view addSubview:info.view]; |
Come vedete non c’è traccia di nessuna dichiarazione relativa alla localizzazione, totalmente gestita da sistema. Le due interfacce, ovviamente, possono essere completamente differenti, in quanto risultano a tutti gli effetti come due file XIB separati.
Continua...
Avrei forse dovuto scrivere “Very very very, short snippet”, tuttavia è un ottimo e comodissimo “trucchetto” questo che sto per mostrare. Eseguendo le “due” righe di codice indicate qui sotto, è possibile ottenere l’output di qualsiasi URL e manipolarlo.
Continua...
Eseguire un metodo dopo n secondi
Tutta la famiglia performSelector è davvero interessante e può essere utile in una moltitudine di casi. La sua applicazione più semplice e comune è la seguente:
1 2 3 4 5
| [self performSelector:@selector(myMethod) withObject:nil afterDelay:3];
//
- (void)myMethod {
NSLog(@"Hello World!");
} |
Tuttavia considerate che il “timer” non è preciso. Questa procedura, quindi, va usata quando non è richiesta una “notevole” precisione temporale.
Recuperare la versione dell’applicazione
1 2
| NSString *version = [[[NSBundle mainBundle ] infoDictionary ] objectForKey :@"CFBundleVersion"];
NSLog (@"versione = %@", version ); |
YES, true o TRUE?
Andando a spulciare nel Kernel Apple iPhone è possibile renderci conto che YES, true e TRUE sono in pratica la stessa edentica cosa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| // definizione di YES
#define YES (BOOL)1
#define NO (BOOL)0
// definizione di true
#define true 1
#define false 0
// definizione di TRUE
#if !defined(TRUE)
#define TRUE 1
#endif
#if !defined(FALSE)
#define FALSE 0
#endif |
Almeno per adesso…
Vibrazione
1 2 3
| #import <AudioToolbox/AudioToolbox.h>
//
AudioServicesPlaySystemSound (kSystemSoundID_Vibrate); |
Puntatore CGImageRef a partire da un UIImage
1 2 3 4
| UIImage *heart = [UIImage imageNamed:@"LittleHeart.png"];
CGImageRef image = [heart CGImage];
// L'immagine adesso può essere "rasterizzata" su un CGContextRef
CGContextDrawImage(c, (CGRect){0, 0, 100, 100}, image); |
Animazioni
1 2 3 4 5
| [UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
// ...
[UIView commitAnimations]; |
NSLog
1 2 3
| NSLog(@"NSString object %@ ", myString);
NSLog(@"Float: %f ", myFloat);
NSLog(@"Integer: %i ", myInt); |
Convertitore da RGB a UIColor
1
| #define RGBA(r,g,b,a) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a] |
Passare parametri ad un NSTimer
Sfruttando il parametro userInfo è possibile inviare un puntatore ad un nostro oggetto al metodo richiamato da timer.
1 2 3 4 5 6 7 8 9 10 11 12 13
| [NSTimer scheduledTimerWithTimeInterval :1 target :self selector :@selector(timerMethod ) userInfo :objectPointer repeats :YES];
// ...
-(void)timerMethod :(NSTimer*)timer {
// Recupero il puntatore al mio oggetto
objectPointer = [timer userInfo ];
// oppure
[[timer userInfo ] myMethod ];
int a = [[timer userInfo ] myProperty ];
// che è lo stesso
int a = [objectPointer myProperty ];
} |
Tempo di esecuzione
Ecco un semplice modo per calcolare tempi brevi utili per verificare la velocità di esecuzione del codice:
1 2 3 4
| CFAbsoluteTime initialTime = CFAbsoluteTimeGetCurrent();
// ... code
CFAbsoluteTime finalTime = CFAbsoluteTimeGetCurrent();
NSLog(@"Tempo trascorso %f", finalTime-initialTime); |
Continua...
Vorrei mostrare e discutere alcuni esempi sul come aggiungere e manipolare proprietà in una Classe Objective-C. Un esempio classico, per l’appunto, è il seguente; nella definizione della nostra interfaccia di classe definiamo due proprietà nome e cognome:
Nel file di implementazione inseriamo la dichiarazione @synthesize in modo tale che Xcode produca per noi i metodi getter e setter usati rispettivamente per leggere ed impostare le nostre due proprietà:
1 2 3 4 5 6 7 8
| // MyClass.m
#import "MyClass.h"
@implementation MyClass
@synthesize nome, cognome;
@end |
Quando andremo ad utilizzare la nostra classe MyClass, cioè quando istanziaremo un oggetto di tipo MyClass, possiamo scrive:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // qualsiasi altra classe, come AppDelegate
// nel file .h
#import <UIKit/UIKit.h>
#import "MyClass.h"
@class TestViewController;
@interface TesAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
TestViewController *viewController;
MyClass *miaClasse;
}
// nel file .m
miaClasse = [MyClass alloc ];
miaClasse.nome = @"Giovambattista";
NSLog (@"miaClasse.nome = %@", miaClasse.nome ); |
Oppure, che è equivalente:
1 2 3
| // sempre nel file .m
[miaClasse setNome:@"Undolog"];
NSLog(@"miaClasse.nome = %@",[miaClasse nome]); |
Fin qui tutto bene. Tuttavia potrebbe fuorviare l’equivalenza delle “variabli” interne (ivar) con il nome della proprietà vera e propria. Per capire la differenza, ripropongo lo stesso esempio facendo a meno, questa volta, di @synthesize. Ora, quindi, dovremmo occuparci noi di scrivere i metodi getter e setter. Per sottolineare ulteriormente le differenze, rinominerò le variabili interne inserendo un underscore davanti al nome. Ma vediamo il codice:
A differenza dell’esempio precedente i puntatori alla variabili interne (incapsultate) sono diventati _nome e _cognome. Inoltre troviamo quattro definizioni di metodi che rappresentano le nostre get e set. @property è scomparso, in quanto non serve più.
Vediamo il file di implementazione MyClass.m:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #import "MyClass.h"
@implementation MyClass
// get per "nome"
- (NSString *) nome {
return _nome;
}
// set per "nome"
- (void) setNome : (NSString *)stringaIngresso {
_nome = stringaIngresso;
}
// get per "cognome"
- (NSString *) cognome {
return _cognome;
}
// set per "cognome"
- (void) setCognome : (NSString *)stringaIngresso {
_cognome = stringaIngresso;
}
@end |
Una classe così scritta potrà essere utilizzata esattamente come la precedente, cioè:
1 2 3 4 5 6 7 8
| miaClasse = [MyClass alloc];
miaClasse.nome = @"Giovambattista";
NSLog(@"miaClasse.nome = %@", miaClasse.nome);
// Oppure, che è equivalente:
[miaClasse setNome:@"Undolog"];
NSLog(@"miaClasse.nome = %@",[miaClasse nome]); |
A livello didattico l’abbandono di @synthesize ci ha costretto a scrivere “da soli” i metodi di get e set, evidenziando – anche con l’aggiunta dell’underscore – le differenze tra il nome della proprietà e la sua ivar interna _nome.
A livello funzionale l’uso dei metodi personali get e set permette un reale controllo del dato prima della sua impostazione (o prima della sua lettura) e quindi un reale incapsulamento per proteggere la variabile interna.
Ad esempio sarebbe possibile impedire il passaggio di stringhe vuote alla proprietà nome:
1 2 3 4
| - (void) setNome : (NSString *)stringaIngresso {
if( stringaIngresso == @"" ) stringaIngresso = @"senza nome";
_nome = stringaIngresso;
} |
Ulteriore variante
Se desiderate utilizzare le variabili interne con l’underscore davanti (chi rpoviene da Adobe Actionscript potrebbe essere abituato così) non è necessario abbandonare l’uso della direttiva @synthesize. Xcode permette infatti di “fondere” i metodi sopra indicati:
1 2
| @synthesize nome = _nome;
@synthesize cognome = _cognome; |
Così facendo potremmo usare internamente il puntatore a _nome, “sintetizzato” – verso l’esterno – come proprietà nome. Inoltre, se è vero che l’uso di @synthesize produce la generazione automatica dei metodi (messaggi) di getter e setter, è vero anche che lo fa solo se non li trova, quindi se desiderate “implementare” un vostro metodo di getter e/o setter potete farlo anche se avete usato la direttiva @synthesize.
Allocazioni di memoria
Negli esempi di sopra ho omesso alcuni dettagli importanti ai fini di una reale implementazione. Prima di tutto non ho illustrato nessun metodo init(), utile ai fini dell’inizializzazione dell’oggetto e dei sui valori di default. Inoltre, manca l’aggiunta di un metodo dealloc():
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 33 34 35
| // file MyClass.m
#import "MyClass.h"
@implementation MyClass
- (id) init {
if ( self = [super init ] ) {
_nome = @"Nome preimpostato";
_cognome = @"Cognome preimpostato";
}
return self;
}
- (void) dealloc {
[_nome release ];
[_cognome release ];
[super dealloc ];
}
- (NSString *) nome {
return _nome;
}
- (void) setNome : (NSString *)stringaIngresso {
if( stringaIngresso == @"" ) stringaIngresso = @"senza nome";
_nome = stringaIngresso;
}
- (NSString *) cognome {
return _cognome;
}
- (void) setCognome : (NSString *)stringaIngresso {
_cognome = stringaIngresso;
}
@end |
In futuro vedremo poi i dettagli sulle proprietà readonly, retain, etc…
Continua...
Il nostro sito Web ormai non è visualizzato solo da postazioni PC. Con la diffusione del mobile, grazie soprattutto ad Apple iPhone, l’accesso al sito o blog viene sempre più spesso effettuato da dispositivi mobili di vario tipo. Diventa quindi esigenza di molti Web Developer saper intercettare ed identificare i diversi “agenti”, cioè gli strumenti con cui un utente visualizza (naviga) le nostre pagine.
Continua...
Gli stati UIControlStateSelected o UIControlStateHighlighted non funzionano quando un UIButton è impostato in modalità UIButtonTypeCustom! O meglio, non funzionano come dovrebbero (perchè riservati agli altri tipi di bottone), ad esempio per creare un bottone a due stati: toggle appunto. Se abbiamo creato due immagini (stato1.png e stato2.png) per il nostro bottone, possiamo procedere in questo modo:
1 2 3
| // Nell'header file creiamo una variabile globale che usaremo per
// controllare il toggle state
BOOL toggleFlag; |
Ora creiamo il nostro bottone:
1 2 3 4 5 6 7 8 9
| // Creaiamo un bottone e lo poniamo inizialmente nello stato "stato1.png"
// Modificate initWithFrame:(CGRect){100,100,50,50} con la posizione e
// dimensioni della vostra immmagine
toggleFlag = YES;
UIButton *toggleButton = [[UIButton buttonWithType:UIButtonTypeCustom] initWithFrame:(CGRect){100,100,50,50}];
[toggleButton setTitle:@"" forState:UIControlStateNormal];
[toggleButton setBackgroundImage:[UIImage imageNamed:@"stato1.png"] forState:UIControlStateNormal];
[toggleButton addTarget:self action:@selector(onToggle:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:toggleButton]; |
Quando si clicca sul bottone verrà inviato un messaggio gestito da onToggle:
1 2 3 4 5 6 7
| - (void)onToggle:(id)sender {
// Recupero puntatore al UIButton
UIButton *buttonClicked = (UIButton *)sender;
// Eseguo il toogle
toggleFlag = !toggleFlag;
[buttonClicked setBackgroundImage:[UIImage imageNamed:(toggleFlag) ? @"stato1.png" : @"stato2.png"] forState:UIControlStateNormal];
} |
Continua...
Un’alternativa davvero semplice per eseguire uno streaming di un file mp3 su Apple iPhone potrebbe essere:
Continua...
La sintassi NSLog(@"%@", ... ); funziona ed è utilizzata per ottenere informazioni sugli oggetti, ma non funziona su tipi dato C come struct CGRect o CGPoint, ad esempio. Per poter sfruttare NSLog(@"%@", ... ); anche su struct di tipo C possiamo appoggiarci a funzioni di conversione come NSStringFromCGRect() o NSStringFromCGPoint:
1 2 3 4 5
| CGrect mioRect = (CGRect){10,20,30,40};
CGPoint mioPoint = (CGPoint){32,64};
//
NSLog( @"Info rettangolo: %@", NSStringFromCGRect(mioRect) );
NSLog( @"Info point: %@", NSStringFromCGPoint(mioPoint) ); |
Nello specifico è possibile perfezionare tale procedure scrivendosi delle piccole macro utili come:
1
| #define NSLogRect(rect) NSLog(@"%s: (%0.0f, %0.0f) %0.0f x %0.0f", #rect, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height) |
Oppure:
1 2 3 4
| #define NSLogCGPoint(point) NSLog(@"%s: (%0.0f, %0.0f)", #point point.x, point.y)
CGPoint mioPoint = (CGPoint){32,64};
NSLogCGPoint(mioPoint); |
Che darà come output:
Continua...
Ultimi Commenti
Giovambattista Fazioli: @ale: Come indicato @Kevin vedi sul repo di GitHub: https://github.com/gfazioli/Ch roma-Key
Giovambattista Fazioli: @Kevin: See https://github.com/gfazioli/Ch roma-Key
Kevin: Very nice example – would like to see the .fla too!
Ludovica: Ciao! Ti spiego il mio dubbio. Quando scrivo un post non inserisco immagini nell’articolo (se così...
Marco: ciao @Giovambattista Fazioli, grazie per tutte le delucidazioni di questa ottima guida. Avrei un quesito da...