T#038 – Utilizzo del MapKit (parte 2): Annotation view calcolo della distanza e reverse geocoding
Con molto più ritardo di quello che avevo previsto, ecco la seconda parte del tutorial riguardante il MapKit. Non perdiamoci in convenevoli e veniamo subito al sodo.
Annotation view con immagine personalizzata
Riprendiamo il codice scritto l’altra volta. Eravamo arrivati a creare una annotation view che visualizzava un pin relativo alla sede di Google a Mountain View. Vogliamo ora sostituire il pin con un’immagine personalizzata. Dopo aver scelto quindi l’immagine che più ci aggrada e averla aggiunta tra le risorse dell’applicazione, possiamo modificare il codice che avevamo scritto nel metodo mapView:viewForAnnotation: modificandolo come segue:
1 2 3 4 5 6 7 8 9 10 | - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation { if (annotation == mapView.userLocation) { return nil; } MKAnnotationView *annotationView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"Pin"] autorelease]; annotationView.image = [UIImage imageNamed:@"google.png"]; annotationView.canShowCallout = YES; annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; return annotationView; } |
Come possiamo notare la sostanziale differenza rispetto al codice scritto precedentemente sta nel fatto che non usiamo più MKPinAnnotationView ma utilizziamo una classe più generica (ad essere precisi la superclasse di MKPinAnnotationView) che ci permette di utilizzare un’immagine a nostra scelta caricandola dalle risorse dell’applicazione (in questo caso google.png) al posto del solito pin. Tutto questo lo facciamo con le righe 5 e 6 del precedente codice, mentre tutte le altre istruzioni sono già state spiegate nel tutorial precedente.

Cacolo della distanza tra due punti e reverse geocoding
La prima cosa che dobbiamo effettuare è importare il framework che ci permette di effettuare questa operazione. Apriamo quindi il file prefix (in XCode lo trovate nel gruppo Other Sources) e modifichiamolo in modo da importare il framework CoreLocation. Alle fine il file dovrebbe presentarsi come segue:
1 2 3 4 5 6 | #ifdef __OBJC__ #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> #import <CoreLocation/CoreLocation.h> #import <MapKit/MapKit.h> #endif |
Andiamo ora a creare un nuovo controller (io l’ho chiamato InfoViewController) che si occuperà di gestire la vista che verrà visualizzata quando premiamo sul disclosure button dell’immagine precedente.
Cominciamo subito con l’aprire il file .h che editeremo come segue:
1 2 3 4 5 6 7 8 | @interface InfoViewController : UIViewController <MKReverseGeocoderDelegate> { CLLocationDistance distance; MKReverseGeocoder *reverseGeocoder; } - (id)initWithUserLocation:(CLLocation *)userLocation annotation:(id <MKAnnotation>)annotation; @end |
Qui abbiamo abbiamo semplicemente aggiunto una variabile di tipo CLLocationDistance (che in sostanza è un double) nella quale memorizzeremo la distanza che c’è tra l’utente e la locazione selezionata e un metodo init che ci permette di passare al controller la posizione dell’utente e l’annotazione su cui vogliamo lavorare. Dichiariamo inoltre un puntatore ad un geocoder e rendiamo la classe conforme al protocollo MKReverseGeocoderDelegate. Questo, come vedremo, ci permetterà di venire a conoscenza dell’indirizzo della sede di Google.
Passiamo ora all’implementazione di questa classe. Andiamo perciò a scrivere il metodi initWithUserLocation:annotation: definito nel file .h e ridefinendo la loadView.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | - (id)initWithUserLocation:(CLLocation *)userLocation annotation:(id <MKAnnotation>)annotation { self = [super init]; self.title = annotation.title; CLLocation *location = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude longitude:annotation.coordinate.longitude]; distance = [userLocation getDistanceFrom:location]; reverseGeocoder = [[MKReverseGeocoder alloc] initWithCoordinate:annotation.coordinate]; reverseGeocoder.delegate = self; return self; } - (void)loadView { [super loadView]; UILabel *distanceLabel = [[[UILabel alloc] initWithFrame:CGRectMake(5, 0, 310, 24)] autorelease]; distanceLabel.text = [NSString stringWithFormat:@"Distanza in metri: %.0f", distance]; [self.view addSubview:distanceLabel]; } |
Quello che facciamo nel metodo initWithUserLocation:annotation: non è altro che il calcolo della distanza tra la posizione dell’utente e le coordinate relative all’annotazione (righe 4 e 5) e di inizializzare il geocoder (righe 6 e 7). Per effettuare la prima operazione ci avvaliamo del metodo getDistanceFrom: di CLLocation (attenti però che dalla 3.2 il metodo risulta deprecato e quindi al suo posto bisogna invocare distanceFromLocation:), mentre per inizializzare il geocoder ci basta passargli le coordinate di cui vogliamo sapere l’indirizzo e settargli il delegate.
Nel loadView ci limitiamo a visualizzare in label la distanza calcolata.
Per avviare il geocoding basta semplicemente usare il metodo start, lo andiamo quindi ad aggiungere ridefinendo la viewDidLoad.
1 2 3 | - (void)viewDidLoad { [reverseGeocoder start]; } |
Ora ci manca solo l’implementazione dei metodi per conformarci al protocollo MKReverseGeocoderDelegate. Sono solamente due metodi: uno verrà chiamato quando il geocoding termina con successo, l’altro quando fallisce.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | - (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark { NSDictionary *addressData = placemark.addressDictionary; CGFloat y = 50; for (NSString *key in [addressData allKeys]) { if ([key compare:@"FormattedAddressLines"] != NSOrderedSame) { UILabel *newRow = [[[UILabel alloc] initWithFrame:CGRectMake(5, y, 310, 24)] autorelease]; newRow.text = [NSString stringWithFormat:@"%@: %@", key, [addressData objectForKey:key]]; [self.view addSubview:newRow]; y += 24; } } } - (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error { UILabel *geocoderFail = [[[UILabel alloc] initWithFrame:CGRectMake(5, 50, 310, 24)] autorelease]; geocoderFail.text = @"Reverse geocoding fallito"; [self.view addSubview:geocoderFail]; } |
Nel primo metodo (geocoding terminato con successo) visualizziamo in delle labels tutte le informazioni legate alle coordinate che abbiamo passato al geocoder (indirizzo, città, nazione, …), mentre nel caso fallisca ci limitiamo a visualizzare una label che ci informi di ciò.
Ora è praticamente tutto pronto per essere visualizzato sul nostro simulatore. Dobbiamo solo ricordarci di modificare un metodo di MapViewController in modo che, quando premiamo sul disclosure button utilizziamo il view controller appena creato:
1 2 3 4 | - (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control { InfoViewController *nextViewController = [[[InfoViewController alloc] initWithUserLocation:mapView.userLocation.location annotation:view.annotation] autorelease]; [self.navigationController pushViewController:nextViewController animated:YES]; } |
Questo il risultato sul simulatore…
|
|
… e sull’iPod.
|
|
Come l’altra volta trovate il codice completo su Google code

















Bel tutorial…
Un aggiunta interessante sarebbe quella di spiegare come visualizzare degli indirizzi dati nelle nostre vicinanze. Questo sarebbe fantastico !