Con questo tutorial voglio spiegare come ruotare dinamicamente un’immagine sfruttando un metodo presente in QuartzCore. Per tale dinamismo sfrutteremo inoltre il magnetometro, che ci permetterà di ruotare l’immagine nella direzione di marcia del nostro dispositivo (useremo per questo fine una figura a forma di freccia).
Partiamo con il nostro progetto
Apriamo Xcode e creiamo un nuovo progetto. Dall’elenco dei template disponibili selezioniamo “View-Based Application”. Proseguiamo e chiamiamo il nostro programmino “Rotation”.

Aggiungiamo quindi un’immagine che rappresenti una freccia nel nostro progetto, se non ne avete una potete cercarla su google o eventualmente usare quella che abbiamo usato noi nel nostro esempio (che trovare riportata qui sotto). Includiamola quindi nel nostro progetto semplicemente trascinandola e facendo attenzione a spuntare “Copy items into destination group’s folder (if needed)”, che effettuerà di fatto una copia e non un semplice link a tale file.

Aggiungiamo i framework necessari
Facciamo tasto destro sulla cartella Frameworks -> Add -> Existing Frameworks e selezioniamo “QuartzCore” e “CoreLocation”


Apriamo quindi il nostro header “rotationViewController.h” e aggiungiamo le seguenti righe di codice:
#import
Dichiariamo variabili di istanza e metodi
Sempre in questo file (rotationViewController.h) dobbiamo definire il protocollo di accesso al Magnetometro, che possiamo fare in questo modo:
@interface rotationViewController : UIViewController {
Aggiungiamo quindi questi due oggetti e le relative property:
CLLocationManager *locManager;
UIImageView *imageRotation;
UILabel *courseValue;
}
@property (assign, nonatomic) CLLocationManager *locManager;
@property (retain, nonatomic) IBOutlet UIImageView *imageRotation;
@property (retain, nonatomic) IBOutlet UILabel *courseValue;
“locManager” sarà l’oggetto che istanzieremo per accedere al GPS (nel nostro caso specifico il magnetometro), “imageRotation” è una semplice ImageView che conterrà la nostra immagine da ruotare dinamicamente, “courseValue” è una UILabel che conterrà il valore numerico graficato dalla nostra immagine.
Implementiamo il codice
Ok, ora possiamo spostarci sul nostro file di implementazione per scrivere il codice necessario all’accesso al magnetometro e alla successiva rotazione dell’immagine.
Usiamo la direttiva @syntesize per poter avere a disposizione i setter/getter delle variabili/oggetti definiti nell’header:
@synthesize locManager;
@synthesize imageRotation;
@synthesize courseValue;
Aggiungiamo quindi nel metodo “viewDidLoad” il seguente codice:
[imageRotation setImage:[UIImage imageNamed:@"arrow.png"]];
self.locManager = [[CLLocationManager alloc] init];
locManager.delegate = self;
[locManager startUpdatingHeading];
Con la prima istruzione assegnerò ad “imageRotation” (al momento del caricamento della vista) l’immagine precedentemente importata nel progetto, nel nostro caso l’immagine si chiama arrow.png.
Le tre righe successive istanziano l’oggetto “CLLocationManager” necessario per accedere alle proprietà del GPS/Magnetometro e deleghiamo se stesso come “ricevente” di tutti i messaggi generati da “LLocationManager”. Facciamo partire infine gli aggiornamenti generati dal magnetometro.
Nel nostro caso specifico andremo a leggere la sua proprietà “trueHeading“, un valore numerico che va da 0 a 359, che possiamo sommariamente considerare come:
- valori tra 0 e 44 = Nord
- valori tra 45 e 89 = NordEst
- valori tra 90 e 134 = Est
- valori tra 135 and 179 = SudEst
- valori tra 180 and 234 = Sud
- valori tra 235 and 269 = SudOvest
- valori tra 270 and 314 = ovest
- valori tra 315 and 359 = NordOvest
Andiamo a scrivere ora il metodo che “CLLocationManager” ci mette a disposizione per acquisire tale valore.
#pragma mark -
#pragma mark locManager Methods
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
//acquisico il valore numerico indicante la mia "direzione"
NSString *headValue = [[NSString alloc] initWithFormat:@"%3.0f", newHeading.trueHeading];
//lo converto in intero
int myIntHead = [headValue intValue];
//lo faccio processare al metodo messo a disposizione da quartz facendo ruotare l'immagine
//in base al radiante
[imageRotation setTransform:CGAffineTransformMakeRotation(myIntHead * 3.14159/180)];
courseValue.text = headValue;
[headValue release];
}
“headValue” acquisirà un valore float composto da tre numeri interi restituito dalla proprietà “trueHeading” del Magnetometro. Con [headValute intValue] convertiremo il valore di headValue in intero.
La terza riga è quella che si occupa di rotare l’immagine e vuole come argomento un radiante. Tale radiante lo otteniamo semplicemente moltiplicando il valore di “trueHeading” per π/2.
Nella quarta riga printiamo sul display il valore che stiamo graficando tramite la freccia.
Gestiamo la memoria
Come buona norma per la gestione della memoria rilasciamo tutto quello che abbiamo allocato:
- (void)viewDidUnload {
[locManager stopUpdatingHeading];
self.locManager = nil;
self.imageRotation = nil;
}
- (void)dealloc {
[super dealloc];
[locManager release];
[imageRotation release];
}
Disegnamo la semplice interfaccia grafica della nostra applicazione
Salviamo il lavoro svolto fino ad ora ed apriamo il file “rotationViewController.xib” in InterfaceBuilder (tramite semplice doppio-clik sullo stesso).
Qui dentro dobbiamo aggiungere una ImageView e due Label, una contenente un identificativo ed una connessa ad courseValue.
Ridimensioniamola e colleghiamola a “imageRotation” che abbiamo definito precedentemente all’interno del nostro codice.

Salviamo e proviamo finalmente il nostro progetto.
ATTENZIONE: occorrerà provare direttamente su un device, altrimenti non noteremo alcuna differenza.
Se avete problemi con il tutorial, questo è il nostro file di progetto.









27 Responses to “T#077 – Sfruttiamo Magnetometro e CoreGraphics nelle nostre applicazioni iPhone”
20 Ottobre 2010
Tweets that mention Sfruttiamo Magnetometro e QuartzCore nelle nostre applicazioni iPhone | devAPP -- Topsy.com[…] This post was mentioned on Twitter by Fast-Devs Project, Bubi Devs. Bubi Devs said: T#077 – Sfruttiamo Magnetometro e CoreGraphics nelle nostre applicazioni iPhone: Con questo tutorial voglio spiega… http://bit.ly/a0kr00 […]
20 Ottobre 2010
danieleBel tutorial!! peccato non poterlo usare sul mio iphone2g.
20 Ottobre 2010
uncoolciao daniele, per fare un test con CGAffineTransformMakeRotation si puo’ scrivere un generatore di numeri casuali temporizzato che ruota randomicamente una immagine con tale funzione, dopo o al piu tardi domani lo integro con questa possibilità.
Per chi non ha un magnetometro ma il gps puo’ prendere la proprietà course di CLLocationManager
20 Ottobre 2010
fastsalve, c’è un problema: l’immagine non viene visualizzata sull’iphone xk il nome dell’immagine nel progetto è diversa da quello reale….poi per far andare la figura a nord il valore @%0.1f va bene??
20 Ottobre 2010
uncoolCiao, riguardo che cosa c’è linkato nel progetto come immagine.
Non capisco la seconda domanda: cosa intendi per far andare la figura a nord? Quello lo devi calcolare in quanto trueHeading si riferisce alla “direzione di marcia” dell’iphone
20 Ottobre 2010
FastNel senso che la rosa gira al contrario…solo tenendo l’iPhone a faccia in giu gira in maniera corretta…probabilmente qualche calcolo sbagliato…come si aggiusta??
20 Ottobre 2010
uncoolla freccia non indica il nord, indica l’orientamento dell’iphone rilevato tramite il magnetometro.
Se per esempio stai camminando con il dispositivo in mano e la freccia indica il basso vuol dire che tu (device) ti stai muovendo verso sud.
Puoi far printare a video il valore di headValue in una label.
Ora faccio una modifica al progetto aggiungendo questa parte ed integro le parti di codice modificate
21 Ottobre 2010
uncoolpost e codice di esempio aggiornato
22 Ottobre 2010
marcocon il mio iPhone 3g non funziona è normale?
22 Ottobre 2010
uncoolCiao Marco si è normale, in quanto il 3g non ha integrato il magnetometro.
Si puo’ fare la stessa cosa sul 3g prendendo il proprieta’
course
di CLLocationManager senza usare il metodo
– (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
14 Febbraio 2011
RagazzettoUna domanda :
qualcuno mi sa dire come mai non va su ipad ?
è un problema del mio iPad o c’ è qualcosaltro ?
P.S. : sicuramente la cosa aiuterebbe molto anche fast !
14 Febbraio 2011
Ragazzettonon sarà mica un bug della beta del 4.3 ?
in ogni caso le altre app che hanno portato la bussola sull ipad funzionano !
strano……. molto strano !
4 Aprile 2011
fastsi, infatti aiuterebbe molto sapere xk su ipad non va e come fare la stessa cosa su iphone 3g e magari sapere come fare per indicare anche la direzione della freccia (es.: NO, N, ecc.)
4 Aprile 2011
giorgioCiao, su ipad non ho modo di testarlo, per il 3g quindi senza magnetometro puoi prendere come riferimento i valori delle macrocoordinate (N – NE – E – …) indicati nel primo post e nella
– (void) locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
puoi fare una cosa del genere
NSString *courseString = [[NSString alloc] initWithFormat:@”%3.0f”, newLocation.course];
NSString *oldCourseString = [[NSString alloc] initWithFormat:@”%3.0f”, oldLocation.course];
int myLastCourse = [oldCourseString intValue];
int myIntCourse = [courseString intValue];
oldCourse = myLastCourse;
if (myIntCourse =0) {
[dir setImage:[UIImage imageNamed:@”no.png”]];
[dir setTransform:CGAffineTransformMakeRotation(oldCourse * 3.14159/180)];
}
if (myIntCourse >= 0) {
[dir setImage:[UIImage imageNamed:@”no.png”]];
[dir setTransform:CGAffineTransformMakeRotation(myIntCourse * 3.14159/180)];
}
[courseString release];
[oldCourseString release];
4 Aprile 2011
giorgioovviamente questo codice lo fai eseguire controllando con una BOOL la presenza o meno del magnetometro con una cosa del genere
BOOL magne;
if (locManager.headingAvailable) {
[locManager startUpdatingHeading];
magne = YES;
} else {
magne = NO;
}
4 Aprile 2011
fastmmmm….nn so se ho capito bn ma provo
5 Aprile 2011
giorgiochiedi pure se non ti è chiaro qualcosa che cerco di metterlo giu meglio.
5 Aprile 2011
giorgioovviamente course è una proprieta di CLLocationManager, per ottenerla il gps deve acquisire correttamente almeno 3 dati validi (l’invalid su questa proprietà è -1) per cui per iphone 3g tale meccanismo è testabile solo all’aperto e dopo che avrà acquisito almeno 3 dati validi
5 Aprile 2011
fastnn sarebbe meglio aggiornare questo articolo in modo che funzioni sia su iphone 3g che su iphone 4
5 Aprile 2011
fastoh almeno il source code
5 Aprile 2011
giorgioAggiornero’ source code per farlo andare su 3g (ovviamente sul4 le chiamate sono le stesse e l’attuale funziona) ma appunto non si tratta piu di sfruttare il magnetometro ma si utilizzano proprietà di un oggetto differente.
Postero’ qui una volta aggiornato il codice.
5 Aprile 2011
fastOk, però nn si può fare come un “if” che se è su iphone 3g usa un metodo e su 4 un’altro (in modo da usare il magnetometro sul 4 e il gps sul 3)?
5 Aprile 2011
giorgiocome ti ho scritto prima, setti una variabile di tipo bool
BOOL magne;
nella viewDidLoad metti questo if
//iniziamo a collezionare i dati gps
[locManager startUpdatingLocation];
if (locManager.headingAvailable) {
[locManager startUpdatingHeading];
magne = YES;
} else {
magne = NO;
}
se magne = YES siamo nel caso di iphone 3g, e quindi andiamo ad implementare il metodo
– (void) locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
tale metodo sarà chiamato (in parole povere) ogni qualvolta ci saranno delle coordinate nuove.
all’interno di questo metodo aggiungi:
NSString *courseString = [[NSString alloc] initWithFormat:@”%3.0f”, newLocation.course];
NSString *oldCourseString = [[NSString alloc] initWithFormat:@”%3.0f”, oldLocation.course];
int myLastCourse = [oldCourseString intValue];
int myIntCourse = [courseString intValue];
oldCourse = myLastCourse;
if (myIntCourse =0) {
[dir setImage:[UIImage imageNamed:@”no.png”]];
[dir setTransform:CGAffineTransformMakeRotation(oldCourse * 3.14159/180)];
}
if (myIntCourse >= 0) {
[dir setImage:[UIImage imageNamed:@”no.png”]];
[dir setTransform:CGAffineTransformMakeRotation(myIntCourse * 3.14159/180)];
}
[courseString release];
[oldCourseString release];
è chiaro ed è gia’ stato spiegato all’inizio dell’articolo.
In caso di iphone 3gs/4 il codice rimane invariato, basta implementare il seguente metodo / codice
– (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
//acquisico il valore numerico indicante la mia “direzione”
NSString *headValue = [[NSString alloc] initWithFormat:@”%3.0f”, newHeading.trueHeading];
//lo converto in intero
int myIntHead = [headValue intValue];
//lo faccio processare al metodo messo a disposizione da quartz facendo ruotare l’immagine
//in base al radiante
[imageRotation setTransform:CGAffineTransformMakeRotation(myIntHead * 3.14159/180)];
courseValue.text = headValue;
[headValue release];
}
Domani vedo di aggiornare il sorgente.
6 Aprile 2011
fastok grazie
10 Maggio 2011
fastva bhè..
15 Giugno 2011
FraScusate, mi servirebbe un’informazione urgente. Sto cercando di creare una variabile CLLocation da valori di latitudine e longitudine LONG INT. Come faccio?
10 Luglio 2012
nicolasciao a tutti volevo chiedervi: quando lancio l’applicazione (con i servizi di localizzazione spenti) la bussola non si muove. se li accendo va tutto alla perfezione. volevo chieedere è possibile far ruotare la bussola senza il gps attivo, visto che molte app lo fanno? grazie