Using iOS MapKit and CoreLocation with Delphi Prism and MonoTouch

Let's share the knowledge with your friends

The iPhone Maps application is one of the most useful app for me, we always used this app to track a direction, check the traffic, search for a restaurant, gas station, etc. In this post I will show how to use the MapKit and CoreLocation framework from iOS SDK, both enable mapping related functionality within you application.

The MapKit provide a visual presentation of geographic information using Google Maps data and image, using the MKMapView control, the same use in the iPhone Maps app. The MapKit framework is exposed on MonoTouch via the MonoTouch.MapKit namespace.

CoreLocation works on a number of iOS devices using a variety of technologies to determine position, and using a built-in compass of newer devices provides the heading the device is pointing to. Using this information you can determine where users are and what direction their device is facing, and by monitoring changes to that data how fast they are moving and how far they have traveled. The CoreLocation framework is exposed on MonoTouch via the MonoTouch.CoreLocation namespace, and deal with two types of information: Location and Heading.

I’m assuming you already watch my introductory presentation about iPhone which I explain how to create a simple Web browser using Mono IDE and Interface Builder, on this post I will just go through the implementation.

In order to use MKMapView you just need to drag and drop the control in your window using Interface Builder, also the Segmented Control which will allow you to change the Map type (Standard, Hybrid, Satellite).

Based on the address informed I will add a PIN on the correspondent location, to do add PIN markers to the map requires creating and adding MKAnnotations to the MKMapView. MKAnnotation is a abstract class and need to be implemented in your application, like the code bellow.

	  MyAnnotation nested in AppDelegate = public class(MKAnnotation)
	  private
	    _coordinate: CLLocationCoordinate2D;
	    _title: System.String;
	    _subtitle: System.String;
	  public
	    property Coordinate : CLLocationCoordinate2D read _coordinate write _coordinate;override;
	    property Title: System.String read _title; override;
	    property Subtitle: System.String read _subtitle; override;
     	    // The custom constructor is required to pass the values to this class,
	    // because in the MKAnnotation base the properties are read-only
	    constructor (l: CLLocationCoordinate2D; t: System.String; s: System.String);

	  end;

Now I had to implement the Search Bar delegate to handle events that it generates, it’s not a full implementation of the UISearchDelegate but it implement what is necessary to implement the search behavior.

The Search button will retrieve the latitude/longitude from Google’s web service and add a pin to the map using the MyAnnotation class. Remember you need to sign up a Google Maps key at http://code.google.com/apis/maps/signup.html to use this application, assigned the Google Maps Key to the variable GoogleMapsKey.

	  MySearchBarDelegate nested in AppDelegate = public class(UISearchBarDelegate)
	  private
	    app: AppDelegate;
	    lastResult: MyAnnotation;
	    GoogleMapsKey:String := String.Empty; // Define you google maps key here.
            // Keep a reference to the application so we can access the UIControls.
	   // This needs to be an internal class to access those (private) controls.
	  public
	    constructor (a: AppDelegate);
		// Method for the searchbar to call when the user wants to search,
		// where we create and call the Geocoder class. Note that we are
		// calling it synchronously which is undesirable in a "real"
		// application.
	    method SearchButtonClicked(searchBar: UISearchBar); override;
	  end;

method AppDelegate.MySearchBarDelegate.SearchButtonClicked(searchBar: UISearchBar);
begin

  var g := new Geocoder(GoogleMapsKey);
  var location: CLLocationCoordinate2D;
  searchBar.ResignFirstResponder;
  UIApplication.SharedApplication.NetworkActivityIndicatorVisible := true;

  // synchronous webservice call
  if g.Locate(searchBar.Text, out location) then begin
    if lastResult <> nil then
    begin
      // if there is already a pin on the map, remove it
      app.Map.RemoveAnnotation(lastResult)
    end;

    app.Map.SetCenterCoordinate(location, true);
    var pin := new MyAnnotation(location, searchBar.Text, location.Latitude.ToString + ',' + location.Longitude.ToString);
    app.Map.AddAnnotationObject(pin);
    lastResult := pin;
  end
  else
  begin
    // display a message that the webservice call didn't work
    using alert := new UIAlertView('Not found', 'No match found for ' + searchBar.Text, nil, 'OK', nil) do
    begin
      alert.Show()
    end
  end;
  UIApplication.SharedApplication.NetworkActivityIndicatorVisible := false;
end;

To finalized here the implementation of the Geo code using Google web services, just passing the address and Google will retrieve the latitude/longitude.

interface

uses
  System,
  System.Xml,
  System.IO,
  System.Net,
  MonoTouch.CoreLocation;

// Documentation for the service http://code.google.com/apis/maps/documentation/geocoding/
type
  Geocoder = public class
  public
    constructor (key: System.String);
    // Sign up for a Google Maps key http://code.google.com/apis/maps/signup.html
  private
    var _GoogleMapsKey: System.String;
    var xmlString: System.String := '';
  public
    method Locate(query: System.String; out return: CLLocationCoordinate2D): System.Boolean;
  private
    // Retrieve a Url via WebClient
    class method GetUrl(url: System.String): System.String;
  end;

implementation

constructor Geocoder(key: System.String);
begin
  _GoogleMapsKey := key;
end;

method Geocoder.Locate(query: System.String; out return: CLLocationCoordinate2D): System.Boolean;
var
  url: System.String := 'http://maps.google.com/maps/geo?q={0}&output=xml&key=' + _GoogleMapsKey;
  coords: XmlNode := nil;
  xd: XmlDocument;
  xnm: XmlNamespaceManager;
  gl: System.String;
  coordinateArray: array of System.String;
begin

  url := String.Format(url, query);
  return := new CLLocationCoordinate2D(0, 0);

  try
    xmlString := GetUrl(url);
    xd := new XmlDocument();
    xd.LoadXml(xmlString);
    xnm := new XmlNamespaceManager(xd.NameTable);
    coords := xd.GetElementsByTagName('coordinates')[0]
  except 

  end;

  if coords <> nil then begin
     coordinateArray := coords.InnerText.Split(',');

     if coordinateArray.Length >= 2 then begin
        gl := Convert.ToDouble(coordinateArray[1].ToString()).ToString + ',' + Convert.ToDouble(coordinateArray[0].ToString());

      return := new CLLocationCoordinate2D(Convert.ToDouble(coordinateArray[1].ToString()),
                                            Convert.ToDouble(coordinateArray[0].ToString()));
      exit true
     end
  end;

  exit false
end;

class method Geocoder.GetUrl(url: System.String): System.String;
var
  return: System.String := System.String.Empty;
  client: System.Net.WebClient := new WebClient();
begin

  using strm: Stream := client.OpenRead(url) do
  begin
    var sr: StreamReader := new StreamReader(strm);
    return := sr.ReadToEnd()
  end;

  exit return;
end;

end.

When we run the application we can have three different views of the Map, the pin (in red) is the location of our office here in Scotts Valley.

The source code is available at CodeCentral.


Let's share the knowledge with your friends
7 replies
  1. Franz-Leo Chomse
    Franz-Leo Chomse says:

    I’m new to Mac programming. When I try to run your example I get the error

    SDK version 4.1 is not installed.

    How can I change the needed SDK version?

    Regards from Germany

    Franz-Leo

    Reply
  2. roberto reyes
    roberto reyes says:

    datasets actualicen bien, tanto el maestro como el detalle y no mande el mensaje “columna desconocida” para los campos del detalle (hay alguna forma de indicarle el nombre de la base de datos que debe actualizar el detalle )?

    (de ser posible favor de e)nviar un ejemplo

    Reply
    • Andreano Lanusse
      Andreano Lanusse says:

      Roberto,

      El DataSetProvider trae un evento llamado OnGetTableName donde el parámetro TableName representa el nombre de la tabla a ser actualizada, si quieres cambiar solamente tienes que cambiar el valor desta variable

      Reply

Trackbacks & Pingbacks

  1. […] This post was mentioned on Twitter by Embarcadero Tech. Embarcadero Tech said: Using iOS MapKit and CoreLocation with Delphi Prism and MonoTouch: The iPhone Maps application is one of the mos… http://bit.ly/f8NuXJ […]

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.