Google Maps for iOS module v 1.8.1

General

Tested with Google Maps SDK for iOS ver 1.6.1 and Titanium SDK version 3.1.3.GA, newer of versions of each SDK may work but are unsupported. if you are having problems with the latest SDK version, please download a supported version from: https://developers.google.com/maps/documentation/ios/releases

Please follow Google's terms of service and "Attribution Requirements" as detailed in https://developers.google.com/maps/documentation/ios/intro?hl=en

You will need to go through a one-time setup process and get Google Maps SDK for iOS.

Download SDK

Download the Google Maps SDK to your mac Visit this link: https://developers.google.com/maps/documentation/ios/start under "Getting the Google Maps SDK for iOS"

open the ZIP file somewhere, we will use it in the next step

Obtain a valid license key

to use Google maps you will need to obtain a license key, instructions are here: https://developers.google.com/maps/documentation/ios/start under "Obtaining an API Key"

Make sure you activate the correct service "Google Maps SDK for iOS" and not "Google Maps API v3" which is for Android.

Make sure you enter your app-id (e.g. com.moshemarciano.maptest) and associate it with your license key, or maps will not load!

Also make sure your key is active (status: Inactive will not work), view this for more details: https://developers.google.com/console/help/?csw=1#activatingapis

it might take sometime before your license or app-id association will become active.

Migrate from version 1.0 of the module

These steps are only needed if you are upgrading from version 1.0 of the module. New users can skip to the next section.

  1. Go to your titanium project and remove the googlemaps.framework file and put it in someplace else
  2. Go to the modules folder where you put the com.moshemarciano.googlemaps module
  3. Find the module.xconfig file in this folder and open it
  4. In line #13 there is "OTHER_LDFLAGS=$(inherited) -F "../../Resources"
  5. Replace that with the folder where you put the GoogleMaps.framework file
  6. Keep the googlemaps.bundle file in your project resources folder
  7. Clean your project and rebuild

Put the com.moshemarciano.googleMaps module in place

Usually you will have your modules under this folder

~/Library/Application Support/Titanium/modules/iphone

or

/Library/Application\ Support/Titanium/modules/iphone/

for example, once you open it it should look like this

/Library/Application Support/Titanium/modules/iphone/com.moshemarciano.googlemaps/1.5

Edit module.xcconfig

on your newly created module folder find the module.xcconfig file and edit it

on line #13 you will find a directive that begins with:

OTHER_LDFLAGS=$(inherited) -F YOUR-GOOGLE-FRAMEWORK-FOLDER -framework GoogleMaps

replace the YOUR-GOOGLE-FRAMEWORK-FOLDER with a full (preferably absolute) path of the folder where you opened your Google Maps SDK. It should contain the GoogleMaps.framework folder (it is a big one)

example #1:

OTHER_LDFLAGS=$(inherited) -F"/Users/moshem/Documents/src/ti_modules/ios/googlemaps" -framework GoogleMaps -framework AVFoundation -framework CoreData -framework CoreLocation -framework CoreText -framework GLKit -framework ImageIO -framework OpenGLES -framework QuartzCore -framework SystemConfiguration -weak_library /usr/lib/libc++.dylib

example #2

OTHER_LDFLAGS=$(inherited) -F /Users/someguy/bin/GoogleMaps/GoogleMaps-iOS-1.3.1/ -framework GoogleMaps -framework AVFoundation -framework CoreData -framework CoreLocation -framework CoreText -framework GLKit -framework ImageIO -framework OpenGLES -framework QuartzCore -framework SystemConfiguration -weak_library /usr/lib/libc++.dylib

Create a new Titanium project

in your TiApp.xml you will need to add some lines at the bottom (or use Ti Studio GUI to add the module)

after the SDK version line add the module declaration

<sdk-version>3.1.0.GA</sdk-version>
<modules>
    <module platform="iphone" version="1.5">com.moshemarciano.googlemaps</module>
</modules>

Copy Google Maps SDK files

Final one-time step would be to copy the Google SDK bundle file to your project Resources folder. (Alloy users see next section)

go into the "GoogleMaps.framework" folder and into it's "Resources" sub folder and copy the "GoogleMaps.bundle" into your Project Resources folder

your Titanium project Resources folder should look like

GoogleMaps.bundle/
KS_nav_ui.png
KS_nav_views.png
app.js
iphone/
ui/

Copy Google Maps SDK files (Alloy)

Final one-time step would be to copy the Google SDK bundle file to your project app assets folder.

go into the "GoogleMaps.framework" folder and into it's "Resources" sub folder and copy the "GoogleMaps.bundle" into your Project Resources folder

your Titanium project folder structure should look like

alloy.js
app/
    controllers/
    models/
    styles/
    views/
    assets/
        iphone/
            GoogleMaps.bundle/

Use the module

The suggested way is to start with the module provided app.js file to learn the basics of using the module and the Google Maps API

Place any graphics, including custom markers in the Resources/iphone folder

If you are having trouble see the bottom of the file for the troubleshooting section

Usage reference

Map Creation

    var googlemaps = require('com.moshemarciano.googleMaps');

    googlemaps.licenseKey("BRzljeflsen_TuhKhkKqqa4YTbz398jd2kaA2w");

    var map = googlemaps.createGoogleMap({
        height:500,
        width:300,
        top:50
    });

    // set camera option #1

    map.setCamera ({
        latitude:32.066158,
        longitude:34.77781900000002,
        zoom:6});

    // set camera option #2

    map.setCamera ({
        latitude:32.066158,
        longitude:34.77781900000002,
        zoom:6,
        bearing:80,
        viewingAngle:70});

Map Creation(Alloy > v1.2)

    index.xml
    =========

    <Window title="Window 1">

        <Module id="map" module="com.moshemarciano.googleMaps" method="createGoogleMap"
        width="300" height="300" top="50"/>

    </Window>

    alloy.js
    ========

    var googlemaps = require('com.moshemarciano.googleMaps');
    googlemaps.licenseKey("BRzljeflsen_TuhKhkKqqa4YTbz398jd2kaA2w");

    index.js
    ========

    $.map.setCamera ({
        latitude:51.43580627441406,
        longitude:-0.14256912469863892,
        zoom:15,
        bearing:0,
        viewingAngle:0
    });

Settings

    // Alloy users should use $.map. convention

    map.mapType = "normal"; // normal, hybrid, satellite, terrain
    map.traffic = true;

    map.zoomGestures = true;
    map.scrollGestures = true;
    map.tiltGestures = true;
    map.rotateGestures = true;

    // triggers the iOS "allow location services" alert

    map.myLocation = true;

    // show my location button control

    map.myLocationButton = true;

    // show compass button, only visible when camera bearing != 0

    map.compassButton = true;

    // overrides newer Google SDK behavior of panning map 
    // upon marker tap (unofficial workaround)

    map.cameraMoveOnMarkerTap = true;

    // customInfoWindow: if set, will not show infowindow for marker tap, you need to do so on your side
    // use the mapX and mapY parameters of the various events to figure out
    // where to display your custom window. May be mutual exclusive with cameraMoveOnMarkerTap
    //
    // second option : let the module take care of infoWindow management, you only provide the view
    // see the custom info window section for more details

    map.customInfoWindow = false;

    /* If you use the Google Maps SDK for iOS in your application, you must include the attribution text as part of a legal notices section in your application. Including legal notices as an independent menu item, or as part of an "About" menu item, is recommended. */

    alert (googlemaps.openSourceLicenseInfo);

Getters

    // current map zoom

    alert(map.zoom);

    // current map region

    alert(map.currentRegion);

    // current map type (normal, satellite, terrain, hybrid, none)

    alert(map.mapType);

    // current selected Marker

    alert(map.selectedMarker);

Animation

    map.animateToViewingAngle(45);

    map.animateToZoom(15);

    map.animateToBearing(30);

    map.animateToLocation({latitude:31.066158, 
        longitude:34.87781});

    map.animateToCameraPosition ({
        latitude:25.066158,
        longitude:31.77781900000002,
        zoom:6,
        bearing:80,
        viewingAngle:70});

Camera

    // zooms in, in 1 increments

    map.zoomIn();

    // zooms out, in 1 increments

    map.zoomOut();

    // zoomTo X zoom level

    map.zoomTo(3);

    // set the camera to match location, animation is disabled

    map.setTarget({
        latitude:52.444366455078125,
        longitude:-0.40848709344863892,
        zoom:14}); // zoom is optional

    // fit map to show Paris : NE = North East corner, SW = South West corner

    map.fitBounds({
        NElatitude:48.898581,
        NElongitude:2.2649,
        SWlatitude:48.815907,
        SWlongitude:2.416306,
        padding:0});

    // Shifts the center of the view by the specified number
    // of points in the x and y directions.
    // X grows to the right, Y grows down.

    map.scrollBy(50,300);

Markers

    // create new markers

    var marker = googlemaps.createMarker({
                    title:      "MyMarker",
                    snippet:    "My snippet",
                    tintColor:  "blue", // mutually exclusive with icon property
                    userData:   123,
                    location:   {latitude:35.9348534, longitude:38.9823748923}
    });

    var london = googlemaps.createMarker({
                    title:      "London",
                    snippet:    "My snippet",
                    animated:   true,
                    tappable:   true, // redundant, defaults to true
                    userData:   btn, // pass anthing, including a Ti object
                    location:   {latitude:35.9348534, longitude:38.9823748923},
                    icon:       "icon.png" // loaded from your project resources folder
    });

    var london2 = googlemaps.createMarker({
             title:      "Custom",
             snippet:    "Icon",
             animated:   true,
             iconView:   customIcon, // pass your own custom view as Marker icon
             location:   {latitude:51.69614700317383, longitude:-0.2597615075111389},
             zIndex: 1,
             rotation: 90,
             draggable: true,
             flat: true,
             groundAnchor: {x:0.4, y:0.2}
    });

    // access marker specific properties

    alert (marker.userData);

    // get all marker properties

    alert(marker.data);

    // add marker to map

    map.addMarker(marker);

    // select a marker programmatically (simulate tap)

    map.selectMarker(marker);

    // unselect marker

    map.selectMarker(null);

    // remove one marker

    map.removeMarker(marker);

    // clear map from all markers

    map.clear();

Polylines

    // path data, each two numbers are X,Y coordinate (each number pair)
    // make sure you enter an even number of items

    var pathData = [51.14366455078125, -0.20148709344863892, 30.33434, 21.4095095];

    // first method

    var polyline = googlemaps.createPolyline({
                             path:pathData,
                             color:"yellow",
                             width:10
    });

    // second method - inline path data

    var polyline = googlemaps.createPolyline({
                             path:[51.14366455078125, -0.20148709344863892, 30.33434, 21.4095095],
                             color:"yellow",
                             width:10
    });

    // add to map

    map.addPolyline(polyline);

    // remove from map

    map.removePolyline(polyline);

Circles

    var circle = googlemaps.createCircle({
            radius:10000,
            location: {latitude:51.43580627441406, longitude:-0.14256912469863892},
            color:"black",
            fillColor:"yellow",  
            width:2
    });

    // add to map

    map.addCircle(circle);

    // remove from map

    map.removeCircle(circle);

Polygons

    // path data, each two numbers are X,Y coordinate (each number pair)
    // make sure you enter an even number of items

    var pathData = [51.14366455078125, -0.20148709344863892, 30.33434, 21.4095095];

    // first method
    var polygon = googlemaps.createPolygon({
                            path: newYorkPath,
                            title:"New York State",
                            color:"black",
                            fillColor:"blue",
                            width:2,
                            tappable:true,
    });

    // add to map

    map.addPolygon(polygon);

    // remove from map

    map.removePolygon(polygon);

Events

    /**
     * Called after a tap gesture at a particular coordinate, but only if a marker
     * was not tapped.  This is called before deselecting any currently selected
     * marker (the implicit action for tapping on the map).
     */

    map.addEventListener('tapAtCoordinate',function(e){
        alert(e);
    });

    /**
     * Called after the camera position has changed. During an animation, this
     * delegate might not be notified of intermediate camera positions. However, it
     * will always be called eventually with the final position of an the animation.
     */

    map.addEventListener('changeCameraPosition',function(e){
      Ti.API.info("map event : changeCameraPosition =>" + JSON.stringify(e));
    });

    /**
     * Called after a long-press gesture at a particular coordinate.
     */

    map.addEventListener('longPressAtCoordinate',function(e){
      Ti.API.info("map event : longPressAtCoordinate =>" + JSON.stringify(e));
    });

    /**
     * Called after a marker has been tapped.
     *
     */

    map.addEventListener('tapMarker',function(e){
      Ti.API.info("map event : tapMarker =>" + JSON.stringify(e));

      // check if this is a tap on a specific marker
      if (e.marker == london)
        alert ("London was tapped");
    });

    /**
     * Called after a marker's info window has been tapped.
     */

    map.addEventListener('tapInfoWindowOfMarker',function(e){
      Ti.API.info("map event : tapInfoWindowOfMarker =>" + JSON.stringify(e));
    });

    /**
     * Called repeatedly when the marker is actively being dragged
     */

    map.addEventListener('draggingMarker',function(e){
        Ti.API.info("map event : draggingMarker =>" + JSON.stringify(e));
    });

    /**
     * Called once when the marker dragging starts
     */

    map.addEventListener('dragMarkerBegin',function(e){
        Ti.API.info("map event : dragMarkerBegin =>" + JSON.stringify(e));
    });

    /**
     * Called once when the marker dragging ends
     */

    map.addEventListener('dragMarkerEnd',function(e){
        Ti.API.info("map event : dragMarkerEnd =>" + JSON.stringify(e));
    });

    /**
     * Called after an overlay has been tapped.
     * This method is not called for taps on markers.
     */

    map.addEventListener('tapOverlay',function(e){
        Ti.API.info("map event : tapOverlay =>" + JSON.stringify(e));

        if ( (e.overlayType == "polygon") && (e.title == "New York State") )
            Ti.API.info("Polygon tapped : " + e.title);
    });

Custom Info Window - Experimental

    // we still want the module to handle it

    map.customInfoWindow = false;

    var infoWindow;
    var infoWindowLabel = null;

    // pre-create the view to hold the
    // custom info window

    infoWindow = createInfoWindow();

    function createInfoWindow() {

        infoWindow = null;

        infoWindow = Ti.UI.createView({
            width: 120,
            height: 80,
            backgroundColor:"red",
        });

        infoWindowLabel = Ti.UI.createLabel({
            color: "white",
            text: "Custom",
            width: 100,
            height: 80,
            top:0,
            font:{fontSize:20, fontWeight:'bold'},
            textAlign:'center'
        });

        infoWindow.add(infoWindowLabel);
        return infoWindow;
    }

    // add your own callback to the module
    // the callback will be called every time
    // the user taps on a marker, if this callback
    // is not defined, the default infoWindow will
    // be shown

    // this is EXPERIMENTAL and thus has limitations:
    // 
    // you can't create views or do any fancy UI 
    // stuff inside the callback other than update
    // your pre-created view. this is due to some
    // weird inter-thread issues between Titanium
    // and iOS UI. if you do, your app will crash
    // 
    // also, after the callback a 'tapMarker' event
    // will fire, if you need to handle it as well
    // make sure not to do any blocking UI stuff, such
    // as alert('hi') in the event handler or it might 
    // make the map unresponsive.

    map.InfoWindowCallback = function (e) {

       if (e.marker == marker) {
            infoWindow.backgroundColor = "red";
            infoWindowLabel.text = "Marker 1";
       } else if (e.marker == marker2) {
            infoWindow.backgroundColor = "magenta";
            infoWindowLabel.text = "Marker 2";
       } else if (e.marker == marker3) {
            infoWindow.backgroundColor = "blue";
            infoWindowLabel.text = "Marker 3";
       }

       return infoWindow;
    };

License

license is required per seat, licenses are for current and all future updates of the same version (e.g. 1.1, 1.2, 1.3), next versions of the module (e.g. 2.0,3.0) might be sold seperatly. see LICENSE file in this module for exact details

Troubleshooting

Module not found

if your output looks like this:

[ERROR] : ** BUILD FAILED ** 
[ERROR] : The following build commands failed: 
[ERROR] : Ld build/Debug-iphonesimulator/users\ myApp.app/one\ Two normal i386 [ERROR] : (1 failure

this means that the module is not setup correctly in your environment, review the above instructions, specifically the module.xcconfig issue and try again. for more detailed info, open your project build folder and find the .xcode project file, open it in xcode and try to run it, see the log which usually will be detailed and state "Framework not found"

View this PDF for more clarification : https://dl.dropboxusercontent.com/u/172026/GoogleMapsSDKNotFound.pdf

Bundle file not found or incompatible

if your output looks like this:

[INFO] : [GoogleMapsModule] Google Maps => New map instance 
[ERROR] : The application has crashed with an uncaught exception 'NSInvalidArgumentException'. 
[ERROR] : Reason: [ERROR] : *** -[NSBundle initWithURL:]: nil URL argument 
[ERROR] : Stack trace: [ERROR] : 0 CoreFoundation 0x04fa15c8 __exceptionPreprocess 152 
[ERROR] : 1 libobjc.A.dylib 0x04b428b6 objc_exception_throw 44 
[ERROR] : 2 CoreFoundation 0x04fa13bb 
[NSException raise:format:] 139 
[ERROR] : 3 Foundation 0x023885ce -[NSBundle initWithURL:] 95 
[ERROR] : 4 Foundation 0x02388557 [NSBundle bundleWithURL:] 67

One of Google SDK's cryptic error messages. This means that the GoogleFramework.bundle file is not found by the app at runtime, or it means that the version of the GoogleFramework.bundle file was not updated to the latest version when you updated Google SDK to the latest version, these two should always be of the same version.

Invalid key

[INFO] : 2013-10-09 16:01:19.633 Test test[18227:a0b] ClientParametersRequest failed, 0 attempts remaining (0 vs 5). Error Domain=com.google.HTTPStatus Code=400 "The operation couldnt be completed. (com.google.HTTPStatus error 400.)" UserInfo=0xdd650c0 {data=<CFData 0xdd65ad0 [0x50ecec8]>{length = 145, capacity = 256, bytes = 0x3c48544d4c3e0a3c484541443e0a3c54 ... 3c2f48544d4c3e0a}} 
[INFO] : 2013-10-09 16:01:19.634 Test test[18227:a0b] Google Maps SDK for iOS cannot connect or validate APIKey: Error Domain=com.google.HTTPStatus Code=400 "The operation couldnt be completed. (com.google.HTTPStatus error 400.)" UserInfo=0xdd650c0 {data=<CFData 0xdd65ad0 [0x50ecec8]>{length = 145, capacity = 256, bytes = 0x3c48544d4c3e0a3c484541443e0a3c54 ... 3c2f48544d4c3e0a}} 
[INFO] : 2013-10-09 16:01:19.634 Test test[18227:a0b]

Your key may be invalid for your bundle ID: my.test.app Your Google API key is invalid, or is not correctly paired with your app bundle ID or is not yet activated on Google servers (sometimes takes a few hours, maybe more)

GoogleMaps version mismatch

<Error>: CGContextDrawImage: invalid context 0x0. This is a serious error. This application, or a library it uses, is using an invalid context  and is thereby contributing to an overall degradation of system stability and reliability. This notice is a courtesy: please fix this problem. It will become a fatal error in an upcoming update.

Your GoogleMaps.framework and GoogleMaps.bundle are not of the same version, you need to update both at the same time you download a new version of the Google Maps SDK for iOS

Contact

please send your questions and requests to canufind1@gmail.com

Please don't post technical issues on the Module page @ marketplace.appcelerator.com

if you feel you found a possible bug in the module please include with your bug report a sample project that illustrates the problem, a screenshot of the bug is always helpful.

Change Log

version 1.8.1

Support for Google SDK v 1.8.1
Support for Titanium SDK 3.3.0.GA

version 1.6.1

Support for Google SDK v 1.6.1
Added myLocationButton, compassButton settings
Added map.zoom, map.currentRegion, map.mapType, map.selectedMarker getters
Experimental support for built-in custom info windows

version 1.5.1

Added marker property: flat (skews with map perspective)
Added marker property: rotation (beta note: google seems to have a tap event bug)
Added marker property: draggable
Added events: draggingMarker, dragMarkerBegin, dragMarkerEnd

version 1.5

Support for Google SDK v 1.5.0
Support for iOS7
Support for Titanium SDK 3.1.3.GA
Added support for Titanium Alloy projects
Added userData to the tapInfoWindowOfMarker event
Added Alloy example app in the more_examples folder
Added Marker properties: zIndex, groundAnchor
Bug fixes

version 1.3

Support for Google SDK v 1.3.1
Custom view markers are now supported via the iconView property 
Added animated, tintColor, tappable options to markers
Added tappable support and title property to circle overlays
Support for Camera actions including fitBounds
Added cameraMoveOnMarkerTap setting to override map pan on marker tap
3D Flyby demo added to the sample app.js
Better memory management
Added another example app (window-scenarios.js) to show memory best practices

version 1.2

Support for Google SDK v 1.2.2.3031
Added Polygon,Circle support
Added tapOverlay event
Added selectMarker method

version 1.1

Support for Google SDK v 1.1.2.2533
Added Polyline support
Added visibleRegion reporting on the "changeCameraPosition" event

version 1.0

Initial release