In questa lezione scriveremo un'app in grado di visualizzare
una mappa utilizzando il modulo ti.map
di Appcelerator. La mappa sarà centrata su una coppia di coordinate
predefinite evidenziate da una annotazione (detto anche marker o
pin). Premendo un pulsante sarà possibile ricavare la nostra
posizione attuale, spostare la mappa sulla nostra posizione
e annotarla con un nuovo pin.
Prima di tutto creiamo un Default Alloy Project,
chiamiamolo html_it-map e diamogli come Application ID il valore it.html.map
(ci servirà in seguito, soprattutto su Android). Una volta creato
il progetto apriamo il file tiapp.xml, cerchiamo il tag <modules/>
e sostituiamolo con quanto segue:
<modules>
<module platform="iphone" version="2.0.2">ti.map</module>
<module platform="android">ti.map</module>
</modules>
Se abbiamo sostituito il tag correttamente, cliccando sulla tab Overview
(in basso a sinistra, sotto la finestra con il codice di
tiapp.xml) dovremmo vedere il riquadro dei moduli, precedentemente
vuoto, con la seguente configurazione:
I moduli, argomento della prossima lezione, sono dei componenti
esterni, scritti in codice nativo (Objective-C per iOS e Java per
Android) che permettono di implementare funzioni non previste
dall'SDK ma che possono essere incorporate nei progetti
Titanium.
Normalmente i moduli vengono installati separatamente;
alcuni, forniti direttamente da Appcelerator, sono installati
di default.
Le mappe, essendo oggetti non definiti nel namespace Ti.UI,
non hanno un loro tag Alloy predefinito, ma occorre sfruttare il
tag View e l'attributo ns che indicherà il
namespace JavaScript. Una buona pratica in questo caso è quella di
utilizzare lo spazio dei nomi Alloy.Globals per rendere
accessibile il modulo a tutti i controller dell'applicazione.
Apriamo quindi il file alloy.js e aggiungiamo le seguenti righe di
codice:
Alloy.Globals.Map = require('ti.map');
Alloy.Globals.html_latitude = 41.8885867;
Alloy.Globals.html_longitude = 12.5894068;
La prima istruzione carica il modulo e lo assegna ad una
variabile globale Alloy.Globals.Map che verrà sfruttata
dalla view Alloy come namespace per la creazione della
mappa. Le altre due costanti invece identificano le coordinate
iniziali su cui vogliamo posizionare la mappa
all'avvio della nostra app.
A questo punto osserviamo il contenuto del file index.xml:
<Alloy>
<Window class="container">
<View id="map" ns="Alloy.Globals.Map"></View>
<ImageView id="html_it" onClick="html_it"></ImageView>
<ImageView id="here" onClick="here"></ImageView>
</Window>
</Alloy>
La finestra principale sarà composta da una mappa a schermo
intero e due immagini cliccabili, come se fossero bottoni, che
attiveranno due diverse funzioni: la prima interrogherà il
sistema di geolocalizzazione integrato del dispositivo per
individuare la posizione attuale e riposizionare la mappa, l'altro
pulsante invece punterà alla posizione di default, cioè quella definita
attraverso le costanti Alloy.Globals.html_latitude e Alloy.Globals.html_longitude.
La mappa è definita con il tag View
:
<View id="map" ns="Alloy.Globals.Map"></View>
Non abbiamo indicato attributi, perchè li inseriremo in index.tss,
come stili:
".container": {
backgroundColor:"white"
}
"#map": {
userLocation: true,
animate: true,
top: 0,
region: { latitude: Alloy.Globals.html_latitude, latitudeDelta: 0.05,
longitude: Alloy.Globals.html_longitude, longitudeDelta: 0.05 }
},
"#here" : {
image: "images/center.jpeg",
width:40,
height:40,
bottom : 10,
left : 10,
zIndex:0
}
"#html_it" : {
image: "images/logo.png",
width:40,
height:40,
bottom : 10,
right : 10,
zIndex:0
}
Lo stile associato alla mappa, #map, mostrerà la
mappa della regione intorno alle coordinate che abbiamo
definito in alloy.js.
Una regione si identifica con longitudine, latitudine e due rispettivi delta.
Piu' piccolo è il delta e più ravvicinata sarà la mappa.
Gli stili #here e #html_it rappresentano le
proprietà di due pulsanti creati usando le ImageView.
La proprietà image fa riferimento ad un file salvato dentro il
progetto nella cartella /app/assets/images. Il path
indicato è quindi relativo a /app/assets, che è la directory di
default per le immagini.
I due pulsanti sono collocati in basso (bottom=10
),
rispettivamente a sinistra e destra dello schermo e in primo piano
rispetto alla mappa (zIndex=0
).
index.js
Il controller dovrà creare un marker, cioè una annotazione
che indica una posizione sulla mappa e che normalmente viene
rappresentata da un simbolo che ricorda una puntina da disegno. Oltre alle coordinate, il
marker può contenere informazioni; nel
nostro caso conterrà un titolo, un sottotitolo e un pulsante che sarà mostrato
alla destra del fumetto visualizzato quando cliccheremo
il simbolo dell'annotazione.
var annotation = Alloy.Globals.Map.createAnnotation({
title : "html.it",
subtitle : "HTML.it: corsi, guide, articoli e script",
latitude : Alloy.Globals.html_latitude,
longitude : Alloy.Globals.html_longitude,
rightButton : "images/logo.png",
// questa proprietà viene aggiunta per gestire l'evento click
url : "http://www.html.it"
});
$.map.addAnnotation(annotation);
Alle proprietà standard dell'annotazione, ne aggiungiamo anche una personalizzata,
che chiamiamo url. Quello che vogliamo infatti è intercettare
l'evento click sull'annotazione per poter aprire il browser
e navigare sulla pagina indicata dall'URL. Il modo più semplice e
comodo per farlo è proprio quello di aggiungere una proprietà personalizzata.
Vediamo adesso come gestire l'eventuale click sull'annotazione:
annotation.addEventListener("click", mapClick);
function mapClick(e) {
Ti.API.info(JSON.stringify(e));
if (e.annotation == null) {
return;
}
if (e.clicksource != "rightButton") {
return;
}
var url = e.annotation.url;
if (url.length > 0) {
Ti.Platform.openURL(url);
}
}
La prima istruzione associa all'evento click
dell'annotazione la callback mapClick. Il metodo addEventListener
è infatti usato da un controller per
gestire un evento, dal momento che non abbiamo a disposizione gli
attributi onClick tipici delle viste.
La callback mapClick riceve l'oggetto evento e, e dopo aver verificato che
sia stato generato dalla pressione del pulsante destro, estrae la
proprietà url e la passa al metodo Ti.Platform.openURL,
che aprirà il browser di default.
Vediamo ora come implementare i pulsanti. Il primo, html_it
ha come compito quello di riposizionare la mappa alla
posizione di partenza, quella cioè definita dalle costanti Alloy.Globals.html_latitude
e Alloy.Globals.html_longitude. Il codice dell'handler è
il seguente:
function html_it() {
$.map.region = {
latitude : Alloy.Globals.html_latitude,
latitudeDelta : 0.05,
longitude : Alloy.Globals.html_longitude,
longitudeDelta : 0.05
};
}
Per spostare la mappa da una posizione ad un'altra, è quindi sufficiente
cambiare il valore della proprietà region.
Più complesso invece è il codice per il pulsante here, che
deve rilevare la posizione del dispositivo, puntare la
mappa sulla nuova posizione e aggiungere un nuovo marker.
function here() {
Ti.Geolocation.getCurrentPosition(function(e) {
Ti.API.info(JSON.stringify(e));
if (e.success) {
if (! e.coords) return;
var lat = e.coords.latitude;
var lon = e.coords.longitude;
$.map.region = {
latitude : lat,
latitudeDelta : 0.05,
longitude : lon,
longitudeDelta : 0.05
};
var tmpAnnotation = Alloy.Globals.Map.createAnnotation({
title : "La mia posizione",
subtitle : lat + " " + lon,
latitude : lat,
longitude : lon,
});
$.map.addAnnotation(tmpAnnotation);
}
});
}
Per conoscere la posizione del dispositivo mobile faremo
affidamento alla funzione getCurrentPosition
del namespace
Ti.Geolocation,
che richiede come argomento una funzione di callback che verrà
richiamata quando le coordinate attualiì saranno disponibili.
Tale funzione avrà come argomento un oggetto
contenente, tra le altre, una proprietà success, che indicherà l'esito
della richiesta e le coordinate della posizione
attuale.
Ricavate le coordinate, il codice risposizionerà la mappa,
creerà una annotazione e la visualizzerà in corrispondenza della posizione
attuale.
Geolocalizzazione su iOS
Per poter utilizzare le funzioni di geolocalizzazione su iOS è
obbligatorio aggiungere una delle chiavi di configurazione NSLocationWhenInUseUsageDescription
oppure NSLocationAlwaysUsageDescription
nel file tiapp.xml, nella sezione dedicata a iOS, come
mostrato di seguito:
<ti:app>
<ios>
<plist>
<dict>
<key>NSLocationAlwaysUsageDescription</key>
<string>
Specify the reason for accessing the user's location information.
This appears in the alert dialog when asking the user for permission to
access their location.
</string>
</dict>
</plist>
</ios>
</ti:app>
Nel tag <string>
occorre indicare il messaggio che
apparirà sullo schermo del dispositivo la prima volta che
l'applicazione vorrà accedere ai servizi di geolocalizzazione.
Configurazione delle mappe per Android
Il supporto per le mappe è uno dei punti critici dell'SDK di
Titanium. Infatti, sebbene in genere su Alloy è possibile utilizzare una
sintassi omogenea per entrambe le piattaforme, l'utilizzo delle mappe
per Android richiede una configurazione particolare.
Il modulo Ti.Map utilizza, nella versione Android, le Google
Maps API v2, che richiedono il Google Play Services SDK.
La documentazione ufficiale di Android può essere d'aiuto per capire
come aggiungere nuovi
package all'SDK.
Innanzitutto, dobbiamo trovare la directory in cui è installato l'SDK Android. Per farlo,
si puo' utilizzare l'opzione del menù Preferences di
Titanium Studio e selezionare la voce di configurazione Studio/Platforms/Android.
Troveremo il path della directory dell'SDK vicino alla stringa Android SDK Home. Apriamo questa la sottodirectory tools, e apriamo l'Android
SDK Manager, che ci permetterà di installare ed
eventualmente eliminare i pacchetti di installazione di Android.
Tra gli Extras dovremmo trovare i Google Play Services, e potremo selezionarli per l'installazione.
Fatto ciò, è il momento di
richiedere una chiave di attivazione per il servizio mappe. Andiamo quindi
sulla Google Developers
Console, e creiamo un nuovo progetto:
Creato il progetto, sarà necessario attivare le Google Map
Android API v2, cercandole nella lista dei servizi
disponibili e cliccando sul corrispondente switch a forma di interruttore che si trova a
destra della pagina. Una volta impostato ad "on", vedremo la voce nella
lista in alto dei servizi attivati.
A questo punto occorrerà creare una chiave Google Maps API
e per farlo ci servono due informazioni: il package name,
che è l'Application ID (nel nostro caso it.html.map come suggerito
all'inizio della lezione) e la SHA-1 certificate fingerprint
che si può ottenere sia per il debug che per la distribuzione. In
questo esempio esamineremo la versione per il debug; per quella di
distribuzione, si rimanda alla lettura della guida
per la distribuzione delle app di Appcelerator.
Per ottenere la firma SHA-1 del certificato di debug dobbiamo
aprire un terminale ed eseguire il seguente comando:
[code]keytool -list -v -keystore ~/Library/Application\ Support/Titanium/mobilesdk/osx/3.4.0.GA/android/dev_keystore[/code]
Questo comando potrebbe variare in base alla versione dell'SDK
installata e di sistema operativo. Occorre inoltre controllare
dove è stato installato l'SDK di Titanium e soprattutto l'ultima
versione installata. Se venisse richiesta una password dopo aver
digitato il comando, sarà necessario premere Invio. Se tutto è andato bene, il precedente comando produrrà
una stringa simile alla seguente:
[code]SHA1: 74:74:37:E3:2D:78:CE:42:E7:04:57:9D:DF:B3:2D:35:CE:18:AA:E6[/code]
A questo punto, dalla Google Developer Console selezioniamo APIs &
auth/Credentials e poi Create new Key. Si aprirà una finestra
popup con una vari tipi di chiavi; selezioniamo Android
Key. Apparirà la seguente finestra:
Inseriamo adesso il codice SHA seguito dal package name, separando queste due stringhe
da un punto e virgola (;) e avremo creato la chiave.
Una volta ottenuta la chiave occorrerà modificare nuovamente il file tiapp.xml
ed inserire il seguente manifest all'interno della
sezione Android del file di configurazione:
<android xmlns:android="http://schemas.android.com/apk/res/android">
<manifest>
<!-- Allows the API to download data from Google Map servers -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- Allows the API to cache data -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- Use GPS for device location -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- Use Wi-Fi or mobile connection for device location -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- Allows the API to access Google web-based services -->
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
<!-- Specify OpenGL ES 2.0 as a requirement -->
<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
<!-- Replace <com.domain.appid> with your application ID -->
<uses-permission android:name="<com.domain.appid>.permission.MAPS_RECEIVE"/>
<permission android:name="<com.domain.appid>.permission.MAPS_RECEIVE" android:protectionLevel="signature"/>
<application>
<!-- Replace "PASTE YOUR GOOGLE MAPS API KEY HERE" with the Google API key you obtained -->
<meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="PASTE YOUR GOOGLE MAPS API KEY HERE"/>
</application>
</manifest>
</android>
È molto importante che le chiavi uses-permission e permission
abbiano il corretto package name (da sostituire al posto di
<com.domain.appid>) e che venga rimpiazzata la scritta PASTE
YOUR GOOGLE MAPS API KEY HERE con la chiave ottenuta dalla
console di Google.
Appcelerator mette a disposizione un documento
ufficiale per la configurazione delle mappe su Android, consultabile
per maggiori approfondimenti.
Il codice sorgente degli esempi utilizzati è allegato a questo articolo e
disponibile su GitHub.